Check out the code here: BackgroundWorker Threads on CodeProject

This article is for novice and amateur developers who recognize areas of their application that could use threading but don’t want to be burdened with some of the complexity that comes with threading. Threading is a concept that many programmers tend to avoid because it can be difficult to understand, debug and implement. You can develop a very sophisticated multi-threaded application using C#. Don’t worry the BackgroundWorker object makes threading easy to use, even if you don’t want to take the time to understand everything about threading.

When your application loads it runs on a single thread, this is referred to as the UI thread. Generally this is the thread that all of your UI objects have been created on and this is the thread that all of your code execution is performed on. The UI also uses this single thread to paint the UI objects. So when you’re running a long task, like processing some unknown number of MP3 files in a directory your application locks up, the window turns white, the user can’t click any buttons, the title bar changes to “My Cool App (Not Responding)”. So you go back and put in a bunch of Application.DoEvents() calls into you MP3 crunching function and all
is better again… Except not really, the code runs slower now and the form still locks up but only for short spirts. The whole application seems a bit choppy.

What you need to do is do this heavy processing on a different thread. Leave the UI thread free for painting the UI. .Net has made the BackgroundWorker object available to us to simplify threading. This object is designed to simply run a function on a different thread and then call an event on your UI thread when it’s complete. The steps are simple, create a BackgroundWorker object tell it what function to run on the Background thread (the DoWork function), tell it what function to run on the UI thread when the work is complete (the RunWorkerCompleted function), then tell the BackgroundWorker object to go to work.

There is one rule you need to be aware of, you can’t access UI objects on a thread that didn’t create them. Therefore you would receive a runtime error if you wrote the line of code lblStatus.Text = “Processing file 5 of 100”; in the DoWork function. There are two ways around this and I use both in the examples. The BackgroundWorker object resolves this problem by giving us a ReportProgress function which can be called from the Background thread’s DoWork function, this will cause the ProgressChanged event to fire on the UI thread. Now we can access the UI objects on their thread and do what we want. But what if I just need to update a label or disable a button while on the BackGround thread, no problem, using Control.Invoke you can supply some code (in an anonymous function) to be ran on the UI thread, I use this technique in the Asynchronous example to update the Progress Form’s label and progress bar.

Check out how I solve these problems in my CodeProject article: BackgroundWorker Threads

An extension of the .NET Treeview control to provide multiselect capabilites
Why doesn’t .NET have a mutiselect treeview? There are so many uses for one and turning on checkboxes in the treeview is a pretty lousy alternative. I tried some 3rd party treeviews and I think what turned me off the most is that the object model is different than the .NET treeview I’m used to working with. All I want is the standard .NET treeview with a SelectedNodes property as well as a SelectedNode property. After a quick search on CodeProject, I found Jean Allisat’s implementation here TreeViewMultiSelect.asp. I wasn’t satisfied though because some things didn’t behave correctly. For example you click on a node then as you Ctrl+Click on a second node, the first node loses its highlighting until the click operation is completed. Strange. So it looks a little bit choppy, but it works. I started with Jean’s implementation and took it to the next level to try and clean up the UI behaviour a bit.

The “choppy” problem I was having with the original implementation of the multiselect treeview was that we were letting the treeview select and highlight the selected node, while in the overidden events we would deal with manually highlighting other selected nodes. The conclusion I came to was to do all of the highlighting myself and not fight with the treeview. So the first thing we need to do is cripple the treeview so that it can NEVER have a SelectedNode. We do this by overrideing the OnMouseDown, OnBeforeSelect & OnAfterSelect events and setting base.SelectedNode to null as well as setting e.Cancel in some of the events to stop them from processing. We also hide the treeview’s SelectedNode property (with the new keyword) and reimplement our own version.

Now that we have a treeview that is crippled we can implement new logic for selecting node(s). When you click on a node it becomes the SelectedNode and it is highlighted. If you were not holding down a ModifierKey then we can clear the previous selection. If you were holding down the Ctrl ModifierKey then we decide whether to add this node to the selection or remove it if it was already in the selection. If you were holding down the Shift ModifierKey then we have to select all the nodes from the current SelectedNode to this one. All of this logic resides in the SelectNode() helper function.

One gothca here. All of the treeview’s KeyDown messages are processed off of the SelectedNode and since there never is a SelectedNode (we’ve crippled it…) then you can’t use the keyboard to navigate/edit the tree. Well that’s no good… So we have to trap the OnKeyDown event and handle Left, Right, Up, Down, Home, End, Page Up, Page Down, and any alpha-numeric character. Each of these key commands can have different behaviours if the Ctrl or Shift ModifierKey are pressed and possibly different behaviours if a branch is expanded or not.

Check out the code here:
Multiselect Treeview on CodeProject

C# Virtual Tree Implementation

September 18, 2007

Check out this article on The Code Project.

app.gif

Introduction

This is my first my first blog, so I picked a simple topic to talk about: Implementing the simplest virtual treeview possible.

Large data and large metadata are problems I run into on every project I work on, providing search capabilities makes browsing large amounts of information possible, but somewhere you’re going to have a Treeview, a Listview or a Grid that is going to need to present some unknown amount of information. How you handle this unknown is what makes your application scalable, or come to grinding halt.

Making your UI controls “Virtual” is also a important to keep your UI thread from locking up. Don’t load massive amounts of data that will never be viewed or needed. Virtual (in this context) simply means you only load the amount of data that is being displayed to the user, but give visual cues that there is more data. The .NET Listview nativly implements a virtual view so listview items can be loaded on demand, also most popular 3rd party grids have some sort of virtual support, but the .Net Treeview control doesn’t…

Using the code

Synchronous Virtual Treeview

The logic for implementing a synchronous virtual treeview is extremly simple, but doesn’t come without its flaws.

  1. Load your root nodes
  2. Add a child node to each root node with the name VIRT. This causes the treeview to put a plus sign next to each node.
  3. Catch the onBeforeExpand event and if the VIRT node is a child of the node being expanded then replace it with the real children.
  4. Add a VIRT node to each of the newly added children. (unless you know for sure it’s a leaf node)
private void treeVirt1_BeforeExpand( object sender, TreeViewCancelEventArgs e ) 
{ 
   // If the node being expanded contains a virtual node then 
   // we need to load this node's children on demand. If it doesn't 
   // contain a virtual node then we already did it, so do nothing.  

   if( e.Node.Nodes.ContainsKey( VIRTUALNODE ) ) 
   { 
      try 
      { 
         // Do some work to load data. 
         // Note this may take a while and could 
         // be annoying to your user. 
         // See asynchronous version below.  

         // Clear out all of the children 
         e.Node.Nodes.Clear();  

         // Load the new children into the treeview. 
         string[] arrChildren = new string[] { "Grapes", "Apples", "Tomatoes", "Kiwi" }; 
         foreach( string sChild in arrChildren ) 
         { 
            // Be sure to add virtual nodes to new items that "may" 
            // have children.  If you know for sure that your item is 
            // a leaf node, then there's no need to add the virtual node. 
            TreeNode tNode = e.Node.Nodes.Add( sChild ); 
            AddVirtualNode( tNode ); 
         } 
      } 
      catch 
      { 
         // Error occured, reset to a known state 
         e.Node.Nodes.Clear(); 
         AddVirtualNode( e.Node ); 
      } 
   } 
}

This algorithm is very simple to implement and defers load time of data when/if needed. This implementation is good for simple applications where all the data is local and loading a branch of data is quick. However if you need to make a server call to load a branch, that could take… forever… And since you’re doing the work on the UI thread you’ll lock up your entire application. It’s trivial to extend this algorithm to use a background worker thread to load your data asynchrounously.

Asynchronous Virtual Treeview

The logic for implementing an asynchronous virtual treeview is slightly different. Instead of catching the OnBeforeExpand event we catch the OnAfterExpand, we then use a BackgroundWorker thread so we can load the data (time consuming operation) on a thread other than the UI thread. We can’t touch UI objects from other threads, but that’s the beauty of the BackgroundWorker thread, its callback event fires on the UI thread. So when the RunWorkerCompleted event fires you can take the data you loaded on the other thread and create TreeNodes out of it. When the RunWorkerCompleted event runs you’ll need to know what TreeNode initiated the request (this way we know who to add the new nodes to) so we’ll send it “along for the ride” on the BackgroundWorker thread, but remember DO NOT use the TreeNode on the BackgroundWorker thread’s DoWork function. You can’t use UI controls on threads other than the thread that created them. (That’s actually not true, you can use BeginInvoke, maybe that will be my next blog).

  1. Load your root nodes
  2. Add a child node to each root node with the name VIRT. This causes the treeview to put a plus sign next to each node.
  3. Catch the onAfterExpand event
  4. Create a BackgroundWorker thread and pass in the TreeNode and any other needed information to the DoWork function
  5. Perform the timeconsuming operation in the DoWork function on the background thread
  6. Return the original TreeNode as well as the results from the time consuming operation
  7. Get the TreeNode and data from the server in the RunWorkerCompleted event
  8. Remove the VIRT node and replace it with new TreeNodes
  9. Add a VIRT node to each of the newly added children. (unless you know for sure it’s a leaf node)
#region Asynchronous Treeview  

private void treeVirt2_AfterExpand( object sender, TreeViewEventArgs e ) 
{ 
   if( e.Node.Nodes.ContainsKey( VIRTUALNODE ) ) 
   { 
      BackgroundWorker bw = new BackgroundWorker(); 
      bw.DoWork += new DoWorkEventHandler( bw_DoWork ); 
      bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bw_RunWorkerCompleted );  

      object[] oArgs = new object[] { e.Node, "Some information..." }; 
      bw.RunWorkerAsync( oArgs ); 
   } 
}  

private void bw_DoWork( object sender, DoWorkEventArgs e ) 
{ 
   object[] oArgs = e.Argument as object[]; 
   TreeNode tNodeParent = oArgs[0] as TreeNode; 
   string sInfo = oArgs[1].ToString();  

   // Note you can't use tNodeParent in here because 
   // we're not on the the UI thread (see Invoke).  We've only 
   // passed it in so we can round trip it to the 
   // bw_RunWorkerCompleted event.  

   // Use sInfo argument to load the data 
   Random r = new Random(); 
   Thread.Sleep( r.Next( 500, 2500 ) ); 
   string[] arrChildren = new string[] { "Grapes", "Apples", "Tomatoes", "Kiwi" };  

   // Return the Parent Tree Node and the list of children to the 
   // UI thread. 
   e.Result = new object[] { tNodeParent, arrChildren }; 
}  

private void bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) 
{ 
   // Get the Parent Tree Node and the list of children 
   // from the Background Worker Thread 
   object[] oResult = e.Result as object[]; 
   TreeNode tNodeParent = oResult[0] as TreeNode; 
   string[] arrChildren = oResult[1] as string[];  

   tNodeParent.Nodes.Clear();  

   foreach( string sChild in arrChildren ) 
   { 
      TreeNode tNode = tNodeParent.Nodes.Add( sChild ); 
      AddVirtualNode( tNode ); 
   } 
}  

#endregion

Helper Functions

// Add Virtual Node function used above. Simply 
// adds a "Loading..." node with the special "VIRT" 
// name to the parent node being passed in.  

private const string VIRTUALNODE = "VIRT";  

private void AddVirtualNode( TreeNode tNode ) 
{ 
   TreeNode tVirt = new TreeNode(); 
   tVirt.Text = "Loading..."; 
   tVirt.Name = VIRTUALNODE; 
   tVirt.ForeColor = Color.Blue; 
   tVirt.NodeFont = new Font( "Microsoft Sans Serif", 8.25F, FontStyle.Underline); 
   tNode.Nodes.Add( tVirt ); 
}

Points of Interest

Andrew D. Weiss
Software Engineer

me.gif

-asdf

Follow

Get every new post delivered to your Inbox.