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) ListViewItem
s.
It uses a static cache for the ListViewItem
s 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.