WinForms ListView Performance - Initializing checked states
CheckBoxes in a WinForms ListView are of course a bit simpler to use than dealing the underlying Win32 control directly. WinForms deals with the ListView structures and the WM_NOTIFY messages. Of course this comes at a cost. Here's a simple demonstration of how to tweak things for better performance. This is a simple example so the gains aren't monumental but when you add real world complexities to the Checked event handler and the computation of the Checked state of an item. There are real noticeable improvements by paying attention to some simple things.
When you are populating a ListView that has CheckBoxes, you really want to set the Checked property of the ListViewItem before you add it to the list. The reason is that once you have added the item to the ListView.Items collection, WinForms will always defer to the underlying Win32 control to query and set the Checked state of the item. Before it's added to the ListView the ListViewItem maintains the state itself. Calling down into the Win32 control is very expensive since it involved PInvoking SendMessage.
There are 5 examples with steady gains in improvement.
Set the checked state of the item after it is added to the ListView | 1425 milliseconds |
Set the checked state of the item after it is added to the ListView but at least check to see if it should be checked - this saves a marshaled SendMessage call to the Win32 ListView per item | 1250 milliseconds |
Set the checked state of the item before it is added to the ListView | 1106 milliseconds |
Add the items via AddRange and set the checked state of the items after they are added to the ListView - again you see that AddRange is MUCH faster than adding items one at a time. | 343 milliseconds |
Add the items via AddRange and have the checked state set before the items are added | 234 milliseconds |
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace ListViewPerf
{
public partial class Form1 : Form
{
const int ListEntryCount = 5000;
static int seed = 7919; // use a constant for repeatable results
String[] strings;
bool[] checksInit;
DateTime start;
int checkedEvents;
public Form1()
{
Random rand = new Random(seed);
strings = new String[ListEntryCount];
checksInit = new bool[ListEntryCount];
for (int i = 0; i < ListEntryCount; i++)
{
strings[i] = rand.Next().ToString();
checksInit[i] = (rand.Next() % 2) == 0;
}
InitializeComponent();
}
void PreCall()
{
listView1.Items.Clear();
checkedEvents = 0;
start = DateTime.Now;
}
void PostCall(string s)
{
TimeSpan ts = DateTime.Now - start;
MessageBox.Show(s + " took " + ts.TotalMilliseconds.ToString() + " milliseconds\nAnd generated " + checkedEvents.ToString() + " Checked events");
}
enum CheckMode { Before, After, AfterWithTest }
void FillListViewWithStrings(CheckMode checkMode)
{
for (int i = 0; i < ListEntryCount; i++)
{
ListViewItem lvi = new ListViewItem(strings[i]);
bool check = checksInit[i];
switch (checkMode)
{
case CheckMode.After:
listView1.Items.Add(lvi);
lvi.Checked = check;
break;
case CheckMode.Before:
lvi.Checked = check;
listView1.Items.Add(lvi);
break;
case CheckMode.AfterWithTest:
listView1.Items.Add(lvi);
if (check)
{
lvi.Checked = check;
}
break;
}
}
}
private void checkAfter_Click(object sender, EventArgs e)
{
PreCall();
FillListViewWithStrings(CheckMode.After);
PostCall("Check After");
}
private void checkAfterTest_Click(object sender, EventArgs e)
{
PreCall();
FillListViewWithStrings(CheckMode.AfterWithTest);
PostCall("Check After With Test");
}
private void checkBefore_Click(object sender, EventArgs e)
{
PreCall();
FillListViewWithStrings(CheckMode.Before);
PostCall("Check Before");
}
ListViewItem[] BuildListViewItems(bool setChecks)
{
ListViewItem[] items = new ListViewItem[ListEntryCount];
if (setChecks)
{
for (int i = 0; i < ListEntryCount; i++)
{
items[i] = new ListViewItem(strings[i]);
items[i].Checked = checksInit[i];
}
}
else
{
for (int i = 0; i < ListEntryCount; i++)
{
items[i] = new ListViewItem(strings[i]);
}
}
return items;
}
private void addRangeAfter_Click(object sender, EventArgs e)
{
PreCall();
ListViewItem[] items = BuildListViewItems(false);
listView1.Items.AddRange(items);
for (int i = 0; i < ListEntryCount; i++)
{
listView1.Items[i].Checked = checksInit[i];
}
PostCall("AddRange After");
}
private void addRangeBefore_Click(object sender, EventArgs e)
{
PreCall();
ListViewItem[] items = BuildListViewItems(true);
listView1.Items.AddRange(items);
PostCall("AddRange Before");
}
private void listView1_ItemChecked(object sender, ItemCheckedEventArgs e)
{
checkedEvents++;
}
}
}
Comments
- Anonymous
March 20, 2006
Tim Noonan is on a blog-posting&nbsp;roll.&nbsp; Not long ago, he did quite a bit of work to get better... - Anonymous
March 23, 2006
I've been reading a series of ListView performance posts by HippieTim. He's done a really good job of...