Introduction
Sometimes we have to present really large TreeView
s on our forms. Creating and filling up such a large TreeView
takes lot of time and the form is blocked meanwhile - we cannot use the other controls.
You would think that filling up the TreeView
on a separate thread solves the problem. The problem is that multithreading itself is not enough; we have to use some tricks. Here you can see a very simple solution for the problem.
Background: Multithreading
In only a few words, multithreading makes it possible to run several tasks parallel. We would like to use our controls on the form -edit a textbox
for example- while the TreeView
is loading.
With this solution, we use BackgroundWorker that is a library class for multithreading.
The Problem: Simple Multithreading Does Not Work
The easiest way would be simple multithreading:
public void FillTree()
{
for (int i = 0; i < 10; i++)
{
tn = treeView1.Nodes.Add(i.ToString());
for (int j = 0; j < 10; j++)
{
tn.Nodes.Add(((int)(i * 10 + j)).ToString());
System.Threading.Thread.Sleep(100);
}
}
}
private void Form2_Load(object sender, EventArgs e)
{
System.Threading.Thread th = new System.Threading.Thread(FillTree);
th.Start();
}
The only problem with this solution is that it does not work. We get an InvalidOperationException
saying: "Action being performed on this control is being called from the wrong thread. Marshal to the correct thread using Control.Invoke
or Control.BeginInvoke
to perform this action." When you add a node to the TreeView
, you have to be on the right thread. To do so, we will use Control.Invoke
.
The Trick: Multithreading & Invoke
In the previous section, we realised that we need more than simple multithreading. We get an exception if we try to add a node to the TreeView
from a separate thread. What can we do? Add the node from the TreeView
's thread.
To execute an operation in the TreeView
's thread context, we have to call its Invoke
method:
treeView1.Invoke(new Add(Add1), new object[] { i });
Invoke
consumes delegates. Here in this sample, we defined only one delegate
for the two Add*
functions:
public delegate void Add(int i);
Now our solution works as follows: when the form is opening, we start to run the TreView
filling logic on a separate thread. Every time it reaches the point when a new node has to be added to the TreeView
, it calls TreeView
's Invoke
which adds the node from the TreeView
's thread.
Start the new thread with BackgroundWorker.RunWorkerAsync
when the form is loading:
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
Call the filling subroutine in the background thread (we have to handle BackgroundWorker.DoWork
):
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
FillTree();
}
The filling subroutine calls back to the TreeView
's thread when it adds a node:
public void FillTree()
{
for (int i = 0; i < 10; i++)
{
treeView1.Invoke(new Add(Add1), new object[] { i });
for (int j = 0; j < 10; j++)
{
treeView1.Invoke(new Add(Add2), new object[] { i * 10 + j });
}
}
}
For more details, please see the attached source code.
History
- 11th July, 2008: Initial version