Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ListView in VirtualMode and checkboxes

0.00/5 (No votes)
23 Mar 2007 2  
A listview control in VirtualMode is the fastest way when working with a lot of items. Special care, however, should be given for items containing checkboxes.

Screenshot - ListViewVirtualMode1.gif

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 contains ItemIndex
        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()
{
    // You can switch between Virtual and Normal
    bool blnVirtual = true;

    // Get the ListViewItem cache up and running
    int NR = 100000;

    lvi = new ListViewItem[NR];
    for (int intI = 0; intI < lvi.Length; intI++)
        lvi[intI] = new ListViewItem(intI + " test");

    // Check some random items, just for testing
    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.

ListViewVirtualMode2.gif - ListView in NormalMode

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 makes it real fast!!
    this.listView1.RetrieveVirtualItem +=
        new RetrieveVirtualItemEventHandler(
        listView_RetrieveVirtualItem);
    this.listView1.VirtualListSize = lvi.Length;
    this.listView1.VirtualMode = true;

    // This is what you need, for drawing unchecked checkboxes
    this.listView1.OwnerDraw = true;
    this.listView1.DrawItem +=
        new DrawListViewItemEventHandler(listView_DrawItem);

    // Redraw when checked or doubleclicked
    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)
{
    // Get ListView working
    this.listView1 = new ListView();
    this.listView1.Dock = DockStyle.Fill;

    // This is what we want!!
    this.listView1.View = View.List;
    this.listView1.CheckBoxes = true;

    if (blnVirtual)
    {
        // This makes it real fast!!
        this.listView1.RetrieveVirtualItem +=
            new RetrieveVirtualItemEventHandler(
            listView_RetrieveVirtualItem);
        this.listView1.VirtualListSize = lvi.Length;
        this.listView1.VirtualMode = true;

        // This is what you need, for drawing unchecked checkboxes
        this.listView1.OwnerDraw = true;
        this.listView1.DrawItem +=
            new DrawListViewItemEventHandler(listView_DrawItem);

        // Redraw when checked or doubleclicked
        this.listView1.MouseClick += 
            new MouseEventHandler(listView_MouseClick);
        this.listView1.MouseDoubleClick +=
            new MouseEventHandler(listView_MouseDoubleClick);
    }
    else
    {
        // The other way
        this.listView1.Items.AddRange(lvi);
    }

    // Show in main form
    this.Controls.Add(this.listView1);
}

History

As of this writing, it is version 1.0.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here