
Introduction
Loading a ListView control with a lot of items is a very slow process. As of .NET 2.0, the ListView control contains a VirtualMode. With a little extra code, it can speed up the process significantly. However, special care should be taken when using check boxed items.
Background
There are two things to do to make the ListView work in VirtualMode. Set some properties on the ListView control, and make an event handler to 'retrieve' a virtual item.
void listView_RetrieveVirtualItem(object sender,
RetrieveVirtualItemEventArgs e)
{
e.Item = listViewItem;
}
....
void SetupListview(bool blnVirtual)
{
...
this.listView1.VirtualMode = true;
this.listView1.RetrieveVirtualItem +=
new RetrieveVirtualItemEventHandler(listView_RetrieveVirtualItem);
...
}
Using the attached source code
The code shows a test framework where you can switch between Normal and VirtualMode. It measures the time it takes to load 100000 (one hundred thousand) ListViewItems.
It uses a static cache for the ListViewItems to make things simple.
private ListViewItem[] lvi;
...
private void Test()
{
bool blnVirtual = true;
int NR = 100000;
lvi = new ListViewItem[NR];
for (int intI = 0; intI < lvi.Length; intI++)
lvi[intI] = new ListViewItem(intI + " test");
lvi[3].Checked = true;
lvi[5].Checked = true;
lvi[12].Checked = true;
lvi[NR-2].Checked = true;
...
}
.NET 2.0 has a nice Stopwatch, and I used it to measure the performance of loading the items from our cache into the ListView control.
...
Stopwatch stopwatch = new Stopwatch();
stopwatch.Reset();
stopwatch.Start();
SetupListview(blnVirtual);
stopwatch.Stop();
this.Text = "ListView VirtualMode=" + blnVirtual +
" : "+ lvi.Length + " items in " +
stopwatch.ElapsedMilliseconds + " mS";
...
To test the normal performance of the ListView control, the following code is used:
...
this.listView1 = new ListView();
this.listView1.Dock = DockStyle.Fill;
this.listView1.View = View.List;
this.listView1.CheckBoxes = true;
this.listView1.Items.AddRange(lvi);
...
As a result, loading of 100000 cached items in Normal mode took somewhat close to 35 seconds.

The picture in the head of this article shows the time for the same test, which is 15mS. VirtualMode is more than 2000 times faster compared to the normal use of the control.
Points of interest
There are some problems when using checkboxes and VirtualMode. When loading items, only the items which are checked have a visible checkbox. The others don't. Also, a click on a checkbox itself results in nothing. A double-click on an item shows a checked item, only when the item loses focus. These are the things that I found, maybe there are more known problems features.
To make a workaround, I had to set the ListView control for OwnerDrawing, and hooked a click and double-click handler on to the control.
Because there is nothing wrong with the normal ListView, for drawing text of the items, the DrawDefault property is set. For getting the checkbox to show for non-checked items as well, the property e.Item.Checked switches between true and false to get the job done.
void listView_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawDefault = true;
if (!e.Item.Checked)
{
e.Item.Checked = true;
e.Item.Checked = false;
}
}
To handle the single-click on the checkbox, first get the ListViewItem from the position of the mouse-click by using GetItemAt. When a valid item is found, check the position of the mouse against the item to find out if the checkbox was hit. Last of all, do a redrawing of the item by using Invalidate.
void listView_MouseClick(object sender, MouseEventArgs e)
{
ListView lv = (ListView)sender;
ListViewItem lvi = lv.GetItemAt(e.X, e.Y);
if (lvi != null)
{
if (e.X < (lvi.Bounds.Left + 16))
{
lvi.Checked = !lvi.Checked;
lv.Invalidate(lvi.Bounds);
}
}
}
Because double-clicking an item works, only the drawing is handicapped, and we have to do the drawing ourselves.
void listView_MouseDoubleClick(object sender, MouseEventArgs e)
{
ListView lv = (ListView)sender;
ListViewItem lvi = lv.GetItemAt(e.X, e.Y);
if(lvi!=null)
lv.Invalidate(lvi.Bounds);
}
Prepare the ListView control to use the event handlers:
...
this.listView1.RetrieveVirtualItem +=
new RetrieveVirtualItemEventHandler(
listView_RetrieveVirtualItem);
this.listView1.VirtualListSize = lvi.Length;
this.listView1.VirtualMode = true;
this.listView1.OwnerDraw = true;
this.listView1.DrawItem +=
new DrawListViewItemEventHandler(listView_DrawItem);
this.listView1.MouseClick +=
new MouseEventHandler(listView_MouseClick);
this.listView1.MouseDoubleClick +=
new MouseEventHandler(listView_MouseDoubleClick);
...
Bind this all together to have a ListView working in both modes.
void SetupListview(bool blnVirtual)
{
this.listView1 = new ListView();
this.listView1.Dock = DockStyle.Fill;
this.listView1.View = View.List;
this.listView1.CheckBoxes = true;
if (blnVirtual)
{
this.listView1.RetrieveVirtualItem +=
new RetrieveVirtualItemEventHandler(
listView_RetrieveVirtualItem);
this.listView1.VirtualListSize = lvi.Length;
this.listView1.VirtualMode = true;
this.listView1.OwnerDraw = true;
this.listView1.DrawItem +=
new DrawListViewItemEventHandler(listView_DrawItem);
this.listView1.MouseClick +=
new MouseEventHandler(listView_MouseClick);
this.listView1.MouseDoubleClick +=
new MouseEventHandler(listView_MouseDoubleClick);
}
else
{
this.listView1.Items.AddRange(lvi);
}
this.Controls.Add(this.listView1);
}
History
As of this writing, it is version 1.0.