Introduction
This is another article about reducing flicker in the ListView. The original article I wrote only works in Windows XP (with manifest file to use Common Controls version 6). You can view it by clicking on this link ListViewXP
This article will focus on two other techniques. Neither of which require Windows XP. Please note that the focus of this article is to dynamically add multiple items with a well-sized image-list without flicker.
WndProc Technique
The first technique involves catching and manipulating messages. As I mentioned in my previous article, every time you add an item to the ListView
, the entire control becomes invalidated. When adding multiple items in a loop, the control only paints when the loop ends unless you add Update()
or Refresh()
(or other various methods) to the end of the loop. Using Update(), Refresh()
etc, will cause flickering because the control is constantly invalidated and repainting itself. One way to observe this is to override the WndProc
method, and store all messages inside an ArrayList
, or another structure. Then when you view the ArrayList
, you will see the numbers corresponding to WM_ERASEBKGND
and WM_PAINT
. We can use this knowledge to help us.
Part of our design of this new class is to allow the programmer to specify that he/she is adding a new item. We create a new method called UpdateItem(int iIndex)
. The programmer can call this, and it will draw the newly created item only.
public void UpdateItem(int iIndex)
{
updating = true;
itemnumber = iIndex;
this.Update();
updating = false;
}
First we have a bool
private member variable called updating. This will be used in the WndProc
method below. When it is set to true, the WndProc
method will catch messages. When false
, WndProc
does nothing except to call base.WndProc()
. itemnumber
is another newly added private member. It holds the index of the newly added item. Update()
is used to redraw invalidated regions of the control (which we play with in WndProc
), and finally it sets updating to be false so that messages are no longer being caught by our code. Now we override WndProc
in order to make our adjustments.
protected override void WndProc(ref Message messg)
{
if (updating)
{
if ((int)WM.WM_ERASEBKGND == messg.Msg)
messg.Msg = (int) WM.WM_NULL;
else if ((int)WM.WM_PAINT == messg.Msg)
{
RECT vrect = this.GetWindowRECT();
ValidateRect(this.Handle, ref vrect);
Invalidate(this.Items[itemnumber].Bounds);
}
}
base.WndProc(ref messg);
}
As you can see, we capture the WM_ERASEBKGND
message, and we convert it to a NULL
message. This will stop the ListView
from being erased. WM_PAINT
is used to redraw an invalidated area. Since the entire control is invalidated, we need to do a little work. First we Validate the entire viewable area. Doing this will cause WM_PAINT
to do nothing, so we follow up by invalidating only the bounds of the new item. Now when painting occurs, it will only occur for the new item because the rest of the control has been validated.
WM_ERASEBKGND
, and WM_PAINT
are enum'd types representing the int
values of the Window messages. You can view these in the code. ValidateRect
is a Win32 function that allows us to validate a certain region. Since there is no equivalent in .NET, we import this from user32.dll (see code). RECT
is a structure similar to Rectangle
, but is needed for the ValidateRect
function. When we call ValidateRect
, it validates the entire window area of the ListView
. We then follow by using the control's Invalidate method to invalidate the bounds of the new item. Now only the new item's bounds are invalidated. This means that only the new item will be painted. Since we captured WM_ERASEBKGND
and validated the rest of the control, each item will be drawn as it is added (provided that you use the newly added UpdateItem(index)
function). If you don't use UpdateItem()
, WndProc
will do nothing because "updating" variable will always be false.
NOTE - I'd like to give credit to Carlos H Perez. He does something similar in his ListViewEx
control, and part of this idea is based on that work.
Now we can do this (in our main application)
for (int i=0; i < 500; i++)
{
ListViewItem lvi =
new ListViewItem("Item #" + i.ToString(), indexOfImage);
listViewFF.Items.Add(lvi);
listViewFF.UpdateItem(i);
}
Now the items will be painted as they are added to the list view without flicker.
Pure .NET Technique
This next technique is pure .NET and you do not need to extend the ListView
class. This technique does not draw the items as they are being added, but describes an alternative way to stop the user from waiting for a long time.
If we do not associate an ImageIndex
with a listView item (use -1 as ImageIndex
), we can add items to the ListView
pretty quickly:
for (int i=0; i < 500; i++)
{
ListViewItem lvi =
new ListViewItem("Item #" + i.ToString(), -1);
this.listView.Items.Add(lvi);
}
Since we are not calling Update()
or Refresh()
, we will not see the items as they are being added, but this will be done pretty fast since no images are associated with the ListView
.
Now all of the items will be added, so we need to associate an Image with them.
for (int i=0; i < 500; i++)
{
ListViewItem lvi = this.listView.Items[i] ;
lvi.ImageIndex = someNumber ;
this.listView.Invalidate(lvi.Bounds);
this.Update();
}
Before this loop begins, all items will be there without an image. Now we simply associate the image, and invalidate the region of the item. Calling Update()
will only redraw the invalidated region. In this case, only the bounds of the item will be invalidated, so the image will be drawn to the screen without affecting the other ListView
items.
This is good for a thumbnail program. You can add the names of the thumbnails in the first loop, and in the second loop, you can load the bitmaps into the imagelist, associate the already created item with the image index of the newly added image, and now each image will be painted as it's being loaded. What makes it even better is that you can throw this second loop inside of a thread. This will allow the user to scroll the listview while images are being painted.
Advantages/Disadvantages of the techniques
You should use each technique depending on what you need. The WndProc
technique allows you to see items as they are being added. If you are adding thousands of items, it may be slower overall, but at least the user can see what's going on. The pure .NET technique will cause the listview to be blank for a few seconds if you try to add (let's say) 5000 items in the first loop, but if the second loop is in a thread, you do not stop the user from doing other things while the images are being drawn.
One thing to note about the WndProc
technique. For some reason, it doesn't work very well if you have a manifest file to use the new XP Themes. I'm not sure why, but some items get erased while others are being drawn. I did find a fix for this (and it is used in source code). Before the loop begins set AutoArrange = false
for the listview. When the loop ends, you can set it back to true
. Of Course, if you're using Windows XP only, you can use the double buffering technique I described in my previous article (See Intro for link to that). If you have no plans to support XP themes, you do not need to mess with the AutoArrange
property.
Note about compiling the source
You will need MS Visual Studio .NET to view this project. After unzipping all the files, there will be two directories, ListViewX and TestListViewFF. Open the solution file in ListViewX folder, and after it loads, set the TestListViewFF project to be the startup project. Then when you compile it should be fine.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.