Introduction
This is 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 a grinding halt.
Making your UI controls "Virtual" is also 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
natively 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 extremely simple, but doesn't come without its flaws.
- Load your root nodes.
- 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.
- Catch the
onBeforeExpand
event and if the VIRT
node is a child of the node being expanded, then replace it with the real children.
- 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( e.Node.Nodes.ContainsKey( VIRTUALNODE ) )
{
try
{
e.Node.Nodes.Clear();
string[] arrChildren = new string[] { "Grapes", "Apples", "Tomatoes", "Kiwi" };
foreach( string sChild in arrChildren )
{
TreeNode tNode = e.Node.Nodes.Add( sChild );
AddVirtualNode( tNode );
}
}
catch
{
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).
- Load your root nodes.
- 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.
- Catch the
onAfterExpand
event.
- Create a
BackgroundWorker
thread and pass in the TreeNode
and any other needed information to the DoWork
function.
- Perform the time consuming operation in the
DoWork
function on the background thread.
- Return the original
TreeNode
as well as the results from the time consuming operation.
- Get the
TreeNode
and data from the server in the RunWorkerCompleted
event.
- Remove the
VIRT
node and replace it with new TreeNodes
.
- 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();
Random r = new Random();
Thread.Sleep( r.Next( 500, 2500 ) );
string[] arrChildren = new string[] { "Grapes", "Apples", "Tomatoes", "Kiwi" };
e.Result = new object[] { tNodeParent, arrChildren };
}
private void bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
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
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 );
}
Andrew D. Weiss
Software Engineer
Check out my Blog: More-On C#

-asdf