Click here to Skip to main content
Click here to Skip to main content

Flicker-free ListView in .NET - Part 2

, 3 Feb 2003
Rate this:
Please Sign up or sign in to vote.
This article discusses a couple of ways to reduce flicker in the .NET Listview.

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)  
    {
        // We do not want to erase the background, 
        // turn this message into a null-message
        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();
            // validate the entire window                
            ValidateRect(this.Handle, ref vrect);

            //Invalidate only the new item
            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] ; 
        // get the already existing item

     lvi.ImageIndex = someNumber ; 
        // Now we associate the item with an image in the imagelist;

     this.listView.Invalidate(lvi.Bounds); 
        // Invalidate the already-existing item
     
    this.Update();  
        // Will force the invalidated region to be redrawn
}

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.

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

Share

About the Author

Giovanni Montrone
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
QuestionMultiline TextBox Control Flickering Problem in C# Pinmemberbishnupatro12-Feb-08 18:05 
Questionthis.Update() returns right away? PinmemberJimmyyy15-May-06 14:52 
AnswerRe: this.Update() returns right away? PinmemberJimmyyy21-May-06 11:26 
GeneralFlicker Free Pinmembercaseylara17-Mar-06 11:47 
GeneralRe: Flicker Free PinmemberTesto28ß522-Jul-09 12:05 
QuestionIt's not working PinmemberRicardo Mendes5-Jan-06 2:07 
AnswerRe: It's not working Pinmemberfputil17-Jan-06 6:27 
GeneralDoubleBuffered Pinmemberdecoder722-Jan-06 5:44 
GeneralRe: DoubleBuffered Pinmemberxlay24-May-06 12:02 
GeneralRe: DoubleBuffered PinmemberMeNot10-Jul-08 2:17 
GeneralChanging BackColor/ForeColor Flicker Free Pinmemberchykun18-Aug-05 16:12 
GeneralNice! PinmemberAndrewVos10-Aug-05 11:45 
QuestionHow does it compare with BeginUpdate and EndUpdate PinmemberDamien Guard26-Nov-04 1:23 
AnswerRe: How does it compare with BeginUpdate and EndUpdate Pinmemberrshearer25-Jan-05 11:12 
GeneralRe: How does it compare with BeginUpdate and EndUpdate PinmemberjoachimF22-Nov-06 1:45 
GeneralI found a better(?) technique PinmemberJordanBortz22-Jun-03 18:14 
GeneralRe: I found a better(?) technique PinsussBjørn Reppen6-Jul-03 10:02 
GeneralIs this technique good only for the list initialization?... PinsussPetru Pau19-Feb-03 0:51 
GeneralRe: Is this technique good only for the list initialization?... PinmemberGiovanni Montrone19-Feb-03 10:51 
GeneralRe: Is this technique good only for the list initialization?... PinmemberPetru6620-Feb-03 0:39 
GeneralComparison to SuspendLayout() PinmemberChristopher Lord5-Feb-03 6:15 
GeneralRe: Comparison to SuspendLayout() PinmemberGiovanni Montrone5-Feb-03 7:29 
GeneralRe: Comparison to SuspendLayout() PinmemberChristopher Lord5-Feb-03 12:54 
GeneralRe: Comparison to SuspendLayout() PinmemberRoger J19-Feb-03 1:34 
GeneralRe: Comparison to SuspendLayout() Pinmembermikasa6-Feb-03 7:14 
GeneralGood Article PinmemberSoliant5-Feb-03 5:27 
GeneralRe: Good Article PinmemberGiovanni Montrone5-Feb-03 7:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 4 Feb 2003
Article Copyright 2003 by Giovanni Montrone
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid