I'm quite new to threads in c# (WPF) and since I've implemented some label and progressbar update successfully, I do not understand Why when I try to add items to the treeView of my GUI from another class called in a separate thread I get an exception:
An unhandled exception of type 'System.InvalidOperationException'
occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object
because a different thread owns it.
My update treeview code is this:
private void updateTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear) {
tree.Dispatcher.Invoke(new Action(() => {
if (clear) {
tree.Items.Clear();
}
ItemCollection treeitems = tree.Items;
foreach (TreeViewItem item in items) {
treeitems.Dispatcher.Invoke(new Action(() => {
treeitems.Add(item);
}));
}
tree.ItemsSource = treeitems;
}));
}
And the exception points at the line:
treeitems.Add(item);
Thanks in advance.
you can use the following :
delegate void DUpdateTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear);
private void UpdataTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear)
{
if (tree.InvokeRequired)
{
DUpdateTreeView d = new DUpdateTreeView(UpdataTreeView);
// replace this by the main form object if the function doesn't in the main form class
this.Invoke(d, new object[] { tree, items, clear });
}
else
{
if (clear)
{
tree.Items.Clear();
}
else
{
// Here you can add the items to the treeView
/***
ItemCollection treeitems = tree.Items;
foreach (TreeViewItem item in items)
{
treeitems.Dispatcher.Invoke(new Action(() =>
{
treeitems.Add(item);
}));
}
tree.ItemsSource = treeitems;
***/
}
}
}
This is a really old question but I figured I would answer it. You have two dispatchers in your sample. You have a treeview that you are getting its thread and a list that seems to be created in a different thread.
But the code should look more like this. Sorry about the VB in this case I'm using a delegate inside the invoke.
tree.Dispatcher.BeginInvoke(Sub()
Dim node = new TreeViewItem() With {.Header = "Header"}
tree.items.add(node)
End Sub)
I am not jumping out of the UI thread to add the node like in the original question.
Related
I'm trying to perform some actions on the selected items in a ListView on a Windows Forms application from a background thread. I've got a delegate method in my code like so:
private delegate ListView.SelectedListViewItemCollection dlgGetSelectedJobs();
private ListView.SelectedListViewItemCollection GetSelectedJobs()
{
if(listViewJobViewer.InvokeRequired)
{
var dlg = new dlgGetSelectedJobs(GetSelectedJobs);
return listViewJobViewer.Invoke(dlg) as ListView.SelectedListViewItemCollection;
}
return listViewJobViewer.SelectedItems;
}
This is being called elsewhere on a background thread using the following:
foreach(ListViewItem job in GetSelectedJobs())
{
// Do stuff
}
However whenever the code enters the foreach loop I get a cross-thread exception and I'm not sure why... Any assistance gratefully received!
Thanks to MongZhu for the assists!
I've managed to work around this issue by altering my delegate method to use Linq to return a List based on the selected items in the ListView:
private delegate List<ListViewItem> dlgGetSelectedJobs();
private List<ListViewItem> GetSelectedJobs()
{
if(listViewJobViewer.InvokeRequired)
{
var dlg = new dlgGetSelectedJobs(GetSelectedJobs);
return listViewJobViewer.Invoke(dlg) as List<ListViewItem>;
}
return (from ListViewItem i in listViewJobViewer.SelectedItems select i).ToList();
}
I'm still not sure why it wouldn't work when I was trying to return the collection, but this seems to work correctly.
private void Invoke()
{
try
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(delegate { Invoke(); }));
}
else
{
removeUc();
}
}
catch (Exception ex)
{
XtraMessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
this is my invoke function im using this because it came from another thread sorry im new here.
private void removeUc()
{
foreach (UserControl uc in fPanel.Controls)
{
fPanel.Controls.Remove(uc);
}
}
and this is my remove function, my problem is for example i have 3 usercontrol yet it only remove 2 it always leaves one usercontrol i want to remove all
The general rule for such situations (language-agnostic) is:
If the collection is known to be array or array-like, i.e. accessible by index, and remove shifts indices of all following items:
→ then you just iterate it backwards (with for not foreach).
If the collection is iterable but assumptions about internal data structure cannot be made:
→ then you create a list/array of elements to be removed, then remove them in a second loop (like in apomene's answer).
private void removeUc()
{
var forRemoval = new List<UserControl>();
//Create removal list
foreach (UserControl uc in fPanel.Controls)
{
forRemoval.Add(uc);
}
//remove from fpanel
foreach (UserControl uc in forRemoval)
{
fPanel.Controls.Remove(uc);
}
}
the following code is used to trigger invoke (code is reduced, i left out the error handling in this example to make it more clear)
public static void InvokeIfNecessary(this Control control, MethodInvoker methodInvoker)
{
if (control != null && !control.IsDisposed && !control.Disposing)
{
if (control.InvokeRequired)
{
control.Invoke(methodInvoker);
}
else
{
methodInvoker();
}
}
}
Normally it works fine, but sometimes if i call a method of a Form an InvalidOperationException is given. Schematic method to be called
// in a Frm2:
internal void UpdateSomething()
{
List<NO> myObjects = frmMain.NO.GetNOs();
if (null != myObjects)
{
this.InvokeIfNecessary(() =>
{
layoutControlGroup.BeginUpdate(); // DevExpress Layoutcontrolgroup
foreach (NO aObject in myObjects)
{
if(...) // if already a control for the object exist update it.
{
// update
}
else
{
// add item
LayoutControlItem layoutControlItem = new LayoutControlItem();
// create new control
Control control = CreateNewControl(aObject);
layoutControlItem.Control = control;
// do some stuff with visibility and size of control
...
layoutControlGroup.AddItem(layoutControlItem); // <-- And here the InvalidOperationException occurs.
/// The message is (translated
/// InvalidOperationException was not handled by usercode
/// The acces on the Control FrmMain was done from another Thrad then the thread which created it.
...;
}
}
...;
layoutControlGroupCA.EndUpdate();
});
}
}
Well... I must admit that i have a conceptual problem here.
Why is the Exception thrown here?
The Frm2 method creates a new element (In NO there is only a string and a struct with strings and bool). The element is only accessed within the UpdateSomething() method. The layOutControlGroup is a member of Frm2.
So in my opinion only a new Control which shoudl be created in Frm2 Thread should be attached to a Frm2 specific Control.
So why does it insisting in FrmMain? (the main form, which calls the method of the form to inform about an update of items)
P.S. this.InvokeIfRequired <- this is Frm2 actually...
So, as we see, there are always trouble with ( begin) invoke.
Use BeginInvoke instead. is my hint.
Usually it says, that begin invoke on controls executes a method in the same thread where the handle was created on.
But as it seems, the handle of form2 is not been created in the same thread, OR perhaps it is not present yet.
Try to check this and verify this, please.
Ah. by the way, flag exceptions, clr, in visual studio, when they are thrown. This helps to spot the error.
I think that you checked InvokeRequired with a wrong control.
What you needed to check with InvokeRequired is layoutControlGroup, but your extension method checks on form's with the code this.InvokeIfNecessary where this is Frm2.
Also, you invoked layoutControlGroup.BeginUpdate() but layoutControlGroupCA.EndUpdate(), seems not symmetric of the usage.
A correction of the code might be:
internal void UpdateSomething() {
var myObjects=frmMain.NO.GetNOs();
if(null!=myObjects) {
MethodInvoker beginUpdate=() => layoutControlGroup.BeginUpdate();
MethodInvoker endUpdate=() => layoutControlGroup.EndUpdate();
layoutControlGroup.InvokeIfNecessary(beginUpdate);
foreach(NO aObject in myObjects)
if(SomeCondition) {
// update
}
else {
LayoutControlItem layoutControlItem=new LayoutControlItem();
Control control=CreateNewControl(aObject);
layoutControlItem.Control=control;
MethodInvoker addItem=
() => {
layoutControlGroup.AddItem(layoutControlItem);
};
layoutControlGroup.InvokeIfNecessary(addItem);
}
layoutControlGroup.InvokeIfNecessary(endUpdate);
}
}
Okay, I have a TreeView that serves as a directory tree for Windows. I have it loading all the directories and functioning correctly, but it is pausing my GUI while it loads directories with many children. I'm trying to implement multithreading, but am new to it and am having no luck.
This is what I have for my TreeView:
private readonly object _dummyNode = null;
public MainWindow()
{
InitializeComponent();
foreach (string drive in Directory.GetLogicalDrives())
{
DriveInfo Drive_Info = new DriveInfo(drive);
if (Drive_Info.IsReady == true)
{
TreeViewItem item = new TreeViewItem();
item.Header = drive;
item.Tag = drive;
item.Items.Add(_dummyNode);
item.Expanded += folder_Expanded;
TreeViewItemProps.SetIsRootLevel(item, true);
Dir_Tree.Items.Add(item);
}
}
}
private void folder_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem item = (TreeViewItem)sender;
if (item.Items.Count == 1 && item.Items[0] == _dummyNode)
{
item.Items.Clear();
try
{
foreach (string dir in Directory.GetDirectories(item.Tag as string))
{
DirectoryInfo tempDirInfo = new DirectoryInfo(dir);
bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);
if (!isSystem)
{
TreeViewItem subitem = new TreeViewItem();
subitem.Header = tempDirInfo.Name;
subitem.Tag = dir;
subitem.Items.Add(_dummyNode);
subitem.Expanded += folder_Expanded;
subitem.ToolTip = dir;
item.Items.Add(subitem);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
Whenever I expand a directory that has a large number of subdirectories, the program appears to be frozen for a few seconds. I would like to display a loading message or animation while it's processing, but I'm not sure how to begin with multithreading. I know I have to use the TreeView's Dispatcher.BeginInvoke method, but other than that I'm kinda lost.
Any help would be greatly appreciated!!!
One of the easiest ways to start a async process is to use an anonymous delegate with BeginInvoke. As an example you could move your code in the constructor to a separate method (say RenderTreeView) and then call it asynchronously to begin a new thread as follows:
Action action = RenderTreeView;
action.BeginInvoke(null, null);
The trick to this is that any time you interact with any UI elements from the async process you need to rejoin the main UI thread, otherwise you will get an exception about cross thread access. This is relatively straight forward as well.
In Windows Forms it's:
if (InvokeRequired)
Invoke(new MethodInvoker({item.Items.Add(subitem)}));
else
item.Items.Add(subitem);
In WPF it's:
if (!Dispatcher.CheckAccess())
Dispatcher.Invoke(new Action(() => item.Items.Add(subitem)));
else
item.Items.Add(subitem);
You really need to break up the code to make it more flexible in terms of methods. At the moment everything is bundled in one method which makes it hard to work with and re-factor for async processes.
Update Here you go :)
public partial class MainWindow : Window
{
private readonly object dummyNode = null;
public MainWindow()
{
InitializeComponent();
Action<ItemCollection> action = RenderTreeView;
action.BeginInvoke(treeView1.Items, null, null);
}
private void RenderTreeView(ItemCollection root)
{
foreach (string drive in Directory.GetLogicalDrives())
{
var driveInfo = new DriveInfo(drive);
if (driveInfo.IsReady)
{
CreateAndAppendTreeViewItem(root, drive, drive, drive);
}
}
}
private void FolderExpanded(object sender, RoutedEventArgs e)
{
var item = (TreeViewItem) sender;
if (item.Items.Count == 1 && item.Items[0] == dummyNode)
{
item.Items.Clear();
var directory = item.Tag as string;
if (string.IsNullOrEmpty(directory))
{
return;
}
Action<TreeViewItem, string> action = ExpandTreeViewNode;
action.BeginInvoke(item, directory, null, null);
}
}
private void ExpandTreeViewNode(TreeViewItem item, string directory)
{
foreach (string dir in Directory.GetDirectories(directory))
{
var tempDirInfo = new DirectoryInfo(dir);
bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);
if (!isSystem)
{
CreateAndAppendTreeViewItem(item.Items, tempDirInfo.Name, dir, dir);
}
}
}
private void AddChildNodeItem(ItemCollection collection, TreeViewItem subItem)
{
if (Dispatcher.CheckAccess())
{
collection.Add(subItem);
}
else
{
Dispatcher.Invoke(new Action(() => AddChildNodeItem(collection, subItem)));
}
}
private void CreateAndAppendTreeViewItem(ItemCollection items, string header, string tag, string toolTip)
{
if (Dispatcher.CheckAccess())
{
var subitem = CreateTreeViewItem(header, tag, toolTip);
AddChildNodeItem(items, subitem);
}
else
{
Dispatcher.Invoke(new Action(() => CreateAndAppendTreeViewItem(items, header, tag, toolTip)));
}
}
private TreeViewItem CreateTreeViewItem(string header, string tag, string toolTip)
{
var treeViewItem = new TreeViewItem {Header = header, Tag = tag, ToolTip = toolTip};
treeViewItem.Items.Add(dummyNode);
treeViewItem.Expanded += FolderExpanded;
return treeViewItem;
}
}
Multithreading may not help much here because the TreeView has to be updated on it's Dispatcher thread.
TreeViews will pause when loading a large number of entries. One way to get around this is to store the contents into an object that mirrored the TreeView structure, and then programmatically load just the first level of the TreeView.
When a user clicks on a node, load the next level of child nodes and expand the node. When that node is collapsed, delete its child nodes to conserve TreeView memory. This has worked well for me. For exceedingly large structures I've used a local Sqlite database (via System.Data.Sqlite) as my backing store and even then the TreeView loaded quickly and was responsive.
You can also look at using BackgroundWorker; that's easiest way of executing an operation on a separate thread(For me :) ).
BackgroundWorker Component Overview: http://msdn.microsoft.com/en-us/library/8xs8549b.aspx
BackgroundWorker Class: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
You can use it with Command pattern as explained here -
Asynchronous WPF Commands
I am loading a big treeview in a seperate thread. This thread starts at the load event of a form.
All goes well, until an error occurs in the load event. When an error occurs I close the form and the thread that loads my treeview must be aborted. But I don't know how to do this.
The problem is, that the form is closed and the thread is still working, so I get an InvalidOperationException. The program breaks down and this part of the thread is highlighted:
tvQuestionnaire.Invoke((MethodInvoker)delegate
{
tvQuestionnaire.Nodes.Add(catNode);
});
The treeview on my form is called tvQuestionnaire. The whole function (which is called in my background worker) looks like this:
private void SetTreeviewData()
{
// Get all categories
List<Category> categories = _questionnaire.GetCategoriesFromQuestionnaire();
// Get all questions which are retrieved by the question manager
OrderedDictionary all_ordered_questions = _questionManager.AllQuestions;
// Store all the questions in a List<T>
List<Question> all_questions = new List<Question>();
foreach (DictionaryEntry de in all_ordered_questions)
{
Question q = de.Value as Question;
all_questions.Add(q);
}
foreach (Category category in categories)
{
// Create category node
TreeNode catNode = new TreeNode();
catNode.Text = category.Description;
catNode.Tag = category;
catNode.Name = category.Id.ToString();
// Get all questions which belongs to the category
List<Question> questions = all_questions.FindAll(q => q.CategoryId == category.Id);
// Default set the font to bold (Windows issue)
Font font = new Font(tvQuestionnaire.Font, FontStyle.Regular);
foreach (Question question in questions)
{
// Create question node
TreeNode queNode = new TreeNode();
queNode.Text = question.Question;
queNode.Tag = question;
queNode.Name = "Q" + question.Id;
queNode.NodeFont = font;
// Determine which treenode icon to show
SetTreeNodeIcon(ref queNode, question);
// Add node to category node
catNode.Nodes.Add(queNode);
}
if (_closing)
return;
// Add category node to treeview
tvQuestionnaire.Invoke((MethodInvoker)delegate
{
tvQuestionnaire.Nodes.Add(catNode);
// Now the category (and thus the questions) are added to treeview
// Set questions treenode icon
//SetTreeNodeIcon(questions);
});
}
// Set each category under its parent
for (int i = tvQuestionnaire.Nodes.Count - 1; i >= 0; i--)
{
Category category = tvQuestionnaire.Nodes[i].Tag as Category;
TreeNode node = tvQuestionnaire.Nodes[i];
if (IsWindow(this.Handle.ToInt32()) == 0)
return;
tvQuestionnaire.Invoke((MethodInvoker)delegate
{
if (category.ParentId == null)
return;
else
{
// Find parent node
TreeNode[] parentNodes = tvQuestionnaire.Nodes.Find(category.ParentId.ToString(), true);
//Remove current node from treeview
tvQuestionnaire.Nodes.Remove(node);
parentNodes[0].Nodes.Insert(0, node);
}
});
}
}
This is the only method that my background worker calls.
So my question is, how can I prevent that the Exception occurs? How do I check the form where the treeview is on, is still 'alive'?
One solution would be to call the CancelAsync method of the backgroundworker (BGW) when you need to close the form. In the DoWork event handler, check at the beginning of the loop that cancellation has not been requested. If it was, exit the loop (and the DoWork handler).
In the form, wait for the BGW to complete (either success or cancellation)
Why not catch this event, than abort the thread's execution?
Check IsHandleCreated property of a form. If the form is still alive, it will true.