Using the CLR 2.0 Virtual List View
I started writing an article about the bugs with the new ListView in virtual mode, but decided that I should first write one about how a virtual ListView actually works. The next post will be about two serious bugs and a simple solution to them.
One of my favorite new features in Whidbey* WinForms is the ability to have virtual ListView controls. The first time I used the feature heavily was when I made an application that displayed large amount of information (log output). Just adding the log lines to the ListView was out of the question for a few important reasons.
1. The first reason is the basic fact that adding ListViewItem instances to the ListView means duplicating at least some of the information – and when we are talking about millions of lines, that’s something you want to avoid.
2. The second is that when you have duplicated data structures, you need to keep them synced in some way. In the case of the above application, there were some filtering capabilities that allowed the user to change which items were displayed. In those cases, the ListView would have had to be regenerated every time (or synced in some other manner) to make sure it displays the correct information.
Virtual ListView limitations
A few things to note when using Virtual ListView controls. It does not support all ListView features. Examples are:
1. Tile View – if you are in Tile view in the ListView, it will change to LargeIcons instead.
2. SelectedItems/CheckedItems – these properties are not supported. Instead, use the SelectedIndices/ChecjedIndices properties to figure out which items exist in those collections.
3. Changing amount of sub items in a ListViewItem instance – when creating ListViewItem instances, you need to make sure that it has the appropriate amount of ListViewSubItem instances in it. With regular list view, you can omit some of them and they will appear empty in the ListView.
4. ArrangeIcons() – this method is not supported.
5. Groups – you cannot use groups in a virtual ListView.
6. Positioning of items – you cannot position ListViewItems in any of the icon modes.
7. Automatic sorting – you cannot use the Sort() method on the ListView or any associated operations.
This is a partial list, but should cover most of the important things.
Basic virtual ListView
With Whidbey and the virtual ListView. The idea is pretty simple – instead of you adding items to the list, you tell the list how many items it has, and it calls back to you when it wants them. In its most basic form, you need only to do the following things when you want to create a virtual ListView:
1. Set the VirtualMode property of the ListView to true.
2. Implement the RetrieveVirtualItem event from which you return a ListViewItem for the supplied index.
3. Set the size of the list by using the VirtualListSize property.
As an example, we will create a ListView that shows information about dates between 01/18/1952 to 04/14/2006 – each line in the ListView will show the date in the single column we have.
For starters, we will create a form and add a ListView control to it. We will set the VirtualMode property of the form to true and implement the RetrieveVirtualItem event of the ListView, the Load event of the Form and we will also add some members that will represent our start and end time:
private DateTime m_start = new DateTime(1952, 1, 18);
private DateTime m_end = new DateTime(2006, 4, 14);
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
e.Item = GetListItem(e.ItemIndex);
}
private ListViewItem GetListItem(int i)
{
DateTime itemTime = m_start + TimeSpan.FromDays(i);
ListViewItem lvi = new ListViewItem(itemTime.ToShortDateString());
lvi.Tag = itemTime;
return lvi;
}
private void Form1_Load(object sender, EventArgs e)
{
TimeSpan span = m_end - m_start;
listView1.VirtualListSize = (int)span.TotalDays;
}
How would it have worked with a regular ListView
With a regular ListView, we would have had to add 19,810 items to the list, take up precious memory, not to even talk about the fact that the amount of time it would take to add all those items, making our UI startup sluggish. In contrast, going the virtual way makes that whole process take virtually no time at all (pun intended). We do get a potential perf hit, depending on the usage scenario, but that perf hit is amortized over time and since a ListView is an object that interacts with a user, it should be near unnoticeable.
Adding some makeup
The ListViewItem objects also support coloring – that can be leveraged in virtual ListViews as well. In our example, when the user double clicks an item in the list, we will give all the other items that are the same day of week a cyan background. For that, we need to make changes to our GetListItem helper method, add another member to the class and add a DoubleClick event to the ListView:
private DayOfWeek? m_dayToHighlight;
private ListViewItem GetListItem(int i)
{
DateTime itemTime = m_start + TimeSpan.FromDays(i);
ListViewItem lvi = new ListViewItem(itemTime.ToShortDateString());
lvi.Tag = itemTime;
if (m_dayToHighlight != null && itemTime.DayOfWeek == m_dayToHighlight.Value)
{
lvi.BackColor = Color.Cyan;
}
return lvi;
}
private void listView1_DoubleClick(object sender, EventArgs e)
{
if (listView1.SelectedIndices.Count == 1)
{
// we cant access the ListView.SelectedItems collection when in
// virtual mode. So we go through the Items collection and the
// SelectedIndices collection.
ListViewItem item = listView1.Items[listView1.SelectedIndices[0]];
DateTime itemTime = (DateTime)item.Tag;
m_dayToHighlight = itemTime.DayOfWeek;
listView1.Refresh();
}
}
How would it have worked with a regular ListView
To get the same functionality with a regular ListView, we would have had to go over all the items in the list and re-sync them with the operation we just did (which means going over each one of the 20,000 items and made sure that the correct color is there).
Optimizing the fetching of items
The RetrieveVirtualItem event will execute more than you think. Excessively so. On average, the event will fire four times for each item visible in a ListView. For the selected items, it may be called more than that. So if you have animation turned in and you page-down the list, you can imagine how many times the event will be fired.
For this reason, the ListView control has an event called CacheVirtualItems which tells you which range of items the control is working on.
To have this functionality we will implement said event, add two members (array of items and an integer that represents what area that array caches) and will change the implementation of the RetrieveVirtualItem event.
private ListViewItem[] m_cache;
private int m_firstItem;
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
// If we have the item cached, return it. Otherwise, recreate it.
if (m_cache != null &&
e.ItemIndex >= m_firstItem &&
e.ItemIndex < m_firstItem + m_cache.Length)
{
e.Item = m_cache[e.ItemIndex - m_firstItem];
}
else
{
e.Item = GetListItem(e.ItemIndex);
}
}
private void listView1_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
{
// Only recreate the cache if we need to.
if (m_cache != null &&
e.StartIndex >= m_firstItem &&
e.EndIndex <= m_firstItem + m_cache.Length)
return;
m_firstItem = e.StartIndex;
int length = e.EndIndex - e.StartIndex + 1;
m_cache = new ListViewItem[length];
for (int i = 0; i < m_cache.Length; i++)
{
m_cache[i] = GetListItem(m_firstItem + i);
}
}
One last thing we need to do though is to invalidate the cache when something changes in our data. For this, we will add a ClearCache method and call it when our data changes:
private void ClearCache()
{
m_cache = null;
}
private void listView1_DoubleClick(object sender, EventArgs e)
{
if (listView1.SelectedIndices.Count == 1)
{
// we cant access the ListView.SelectedItems collection when in
// virtual mode. So we go through the Items collection and the
// SelectedIndices collection.
ListViewItem item = listView1.Items[listView1.SelectedIndices[0]];
DateTime itemTime = (DateTime)item.Tag;
m_dayToHighlight = itemTime.DayOfWeek;
ClearCache();
listView1.Refresh();
}
}
On slow machines, even with simple examples like the one I just gave, you can see an actual perf improvement when making use of the caching event.
Conclusion
When you have a lot of information to display or when the information the list is based on changes a lot, it may be worth it to use the virtual ListView mode to make your life easier. In the next article I will discuss two nasty bugs with the virtual mode that are pretty easy to solve.
* I wonder if we are at that magical period of time when saying “Whidbey” makes you cool. Kind’a like saying “Chicago” did back when Windows 95 came out and “Whistler” back when XP came out.
Edits: Ken Argo found a bug in the CacheVirtualItems event I implemented - the number was off-by-one and would have caused an unnecessery perf impact. Fixed it in the sample. Thanks Ken!
Comments
Anonymous
March 06, 2006
Great article!Anonymous
May 11, 2006
How to sort items in virtual list viewAnonymous
May 13, 2006
Qwerty:
You need to sort your data yourself when in virtual modeAnonymous
May 15, 2006
How?
Sort before I add itemsAnonymous
May 15, 2006
Yeap. Pretty much.Anonymous
June 29, 2006
Prerequisites:
A list of items which are displayed in the Virtual List View.
Say, the size of the item list is 1000 and the items from 365 to 400 are currently displayed in Virtual List View.
Background thread is constantly updating the items data.
Problem:
I want the ListView to be updated every time the data of the items that are currently viewed is changed. I the example above when the data of the items from 365 to 400 is changed I want the view to be updated. I don't want to clear cache all the time, because the rest of 935 items are invisible in ListView.
Question:
How to do that?Anonymous
August 17, 2006
Great article!
Is there a chance to put ICONS to the subItems? Would be very helpful..
Cheers
MartinAnonymous
August 17, 2006
You should be able to do this by employing owner drawn listviews.
Also check out http://codetools.com, traditionally, they have very good solutions for this sort of thing.Anonymous
October 10, 2006
Simple,but ver usefull. I have Listview with more than 20000 items .Gettting data,and loading it to cache array one via backgroundworker take several seconds,i'm going to improve it. The real problem was to add items to Listview - it took up to 30 seconds. Using virtual mode give excelent result - about 0.5 secod and no ui freeze!Anonymous
November 08, 2006
ImageKey property for list items also seems to be not working in a virtual list. You should use ImageIndexAnonymous
March 07, 2007
Very nice article - it helped me alot :)Anonymous
April 25, 2007
Very useful article... Thank youAnonymous
August 06, 2008
设置ListView的VirtualMode为true,然后通过处理RetrieveVirtualItem和CacheVirtualItems事件,即可提高打造出高性能的ListView。Anonymous
November 07, 2008
It is very nice article. I have one problem with virtual list view When current view is LargeIcon then scrolling will takes some time. I think it because of large no. items has to retrieve and their associated images too. Is their any way to tackle this problem?Anonymous
June 16, 2009
Can somebody please include this crucial information in the original ListView.VirtualMode Property MSDN page about VirtualMode's limitations? I spent 4 hours attempting to figure out why ListView set to VirtualMode = true wasn't displaying a ListViewGroup Collection... Virtual ListView limitations
- Groups – you cannot use groups in a virtual ListView. It could surely save other people hours of work attempting to figure out why it's not functioning.