Need help implementing multithreading in my TreeView (C #, WPF)

Ok, I have a TreeView that serves as a directory tree for Windows. I download all directories and work correctly, but it pauses my GUI when it loads directories with lots of children. I am trying to implement multithreading, but I am new to this and I have 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 with a large number of subdirectories, the program freezes for a few seconds. I would like to display a boot message or animation during processing, but I'm not sure how to start with multithreading. I know that I need to use the TreeView Dispatcher.BeginInvoke method, but other than that I am a bit lost.

Any help would be greatly appreciated.

+4
source share
3 answers

One of the easiest ways to start an asynchronous process is to use an anonymous delegate with BeginInvoke. As an example, you can move your code in the constructor into a separate method (say, RenderTreeView), and then call it asynchronously to start a new thread as follows:

 Action action = RenderTreeView; action.BeginInvoke(null, null); 

The trick is that at any time when you interact with any user interface elements from the asynchronization process, you need to connect to the main user interface thread, otherwise you will get an exception from access to cross-threads. It is also relatively straightforward.

On Windows Forms, this is:

 if (InvokeRequired) Invoke(new MethodInvoker({item.Items.Add(subitem)})); else item.Items.Add(subitem); 

In WPF, this is:

 if (!Dispatcher.CheckAccess()) Dispatcher.Invoke(new Action(() => item.Items.Add(subitem))); else item.Items.Add(subitem); 

You really need to break the code to make it more flexible in terms of methods. At the moment, everything is connected in one method, because of which it is difficult to work and refactor for asynchronous 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; } } 
+2
source

Multithreading may not help much here because the TreeView needs to be updated on it with a Dispatcher thread.

TreeViews pauses when loading a large number of records. One way around this is to save the contents to an object that reflects the TreeView structure, and then programmatically load only the first level of the TreeView.

When the user clicks on node, load the next level of child nodes and expand node. When this node is collapsed, delete its child nodes to save TreeView memory. It worked well for me. For extremely large structures, I used the local Sqlite database (via System.Data.Sqlite ) as my backup storage and even then loaded TreeView was fast and responsive.

0
source

You can also look at using BackgroundWorker; this is the easiest way to execute the operation in 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 the Command template as described here -

WPF Asynchronous Commands

0
source

All Articles