2

I'm using listview control with the following parameters set:

        this.listView1.BackColor = System.Drawing.Color.Gainsboro;
        this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
        this.columnHeader1,
        this.columnHeader2});
        this.listView1.FullRowSelect = true;
        this.listView1.HideSelection = false;
        this.listView1.Location = new System.Drawing.Point(67, 192);
        this.listView1.Name = "listView1";
        this.listView1.Size = new System.Drawing.Size(438, 236);
        this.listView1.TabIndex = 0;
        this.listView1.UseCompatibleStateImageBehavior = false;
        this.listView1.View = System.Windows.Forms.View.Details;
        this.listView1.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.listView1_DrawColumnHeader);
        this.listView1.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.listView1_RetrieveVirtualItem);
        this.listView1.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.listView1_DrawSubItem);

Two rows are provided with some random text. Ownerdrawing is simple:

    private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
    {
        if (e.ColumnIndex == 0)
        {
            e.DrawBackground();
            e.DrawText();                
        }
        else
            e.DrawDefault = true;
        //Console.WriteLine("{0}\t\tBounds:{1}\tItem:{2}\tSubitem:{3}", (i++).ToString(), e.Bounds.ToString(), e.Item, e.SubItem);
    }

the problem is: when i hover mouse on listview's content, i get flickering of first column. Debugging shows that DrawSubItem is called constantly while the mouse is over it.

Is it bug? How to avoid this behavour?

silly_dg
  • 105
  • 1
  • 8
  • This is an old question, but the acccepted answer is not correct, or at least not as of .NET 4.0. Check the DoubleBuffered protected attribute of the ListView class, and possibly my answer to [this](http://stackoverflow.com/questions/10484265/flickering-in-listview-control-ownerdraw-virtual/10501938#10501938) question. – zmilojko May 08 '12 at 15:46
  • The answer as given is completely correct. On XP, if you have a virtual list and hover over column 0, the control will flicker. DoubleBuffered = true makes no difference. It is true that on Windows 7, this issue does not happen, but that does not make this answer incorrect. – Grammarian May 11 '12 at 05:00

3 Answers3

6

This is a bug in .NET's ListView and you cannot get around it by double buffering.

On virtual lists, the underlying control generates lots of custom draw events when the mouse is hover over column 0. These custom draw events cause flickering even if you enable DoubleBuffering because they are sent outside of the normal WmPaint msg.

I also seem to remember that this only happens on XP. Vista fixed this one (but introduced others).

You can look in at the code in ObjectListView to see how it solved this problem.

If you want to solve it yourself, you need to delve into the inner plumbing of the ListView control:

  1. override WndProc
  2. intercept the WmPaint msg, and set a flag that is true during the msg
  3. intercept the WmCustomDraw msg, and ignore all msgs that occur outside of a WmPaint event.

Something like this::

protected override void WndProc(ref Message m) {
    switch (m.Msg) {
        case 0x0F: // WM_PAINT
            this.isInWmPaintMsg = true;
            base.WndProc(ref m);
            this.isInWmPaintMsg = false;
            break;
        case 0x204E: // WM_REFLECT_NOTIFY
            NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
            if (nmhdr.code == -12) { // NM_CUSTOMDRAW
                if (this.isInWmPaintMsg)
                    base.WndProc(ref m);
            } else
                base.WndProc(ref m);
            break;
        default:
            base.WndProc(ref m);
            break;
    }
}
Grammarian
  • 6,774
  • 1
  • 18
  • 32
  • In my case, I overrode each OnDrawXXX function and prefaced each function with a check for (this.isInWmPaintMsg == true). Like this method, it fixed the problem for me (at least on Win7). I removed the case 0x204E, which is a good solution, but it relies on the .NET-internal WM_REFLECT constant being defined at 0x2000. While this is almost certain to never change, WM_PAINT will absolutely NEVER change. Found this on google, many thanks for the pointers! – Michael Aug 31 '10 at 08:27
  • WM_REFLECT_NOTIFY isn't .NET specific. It's been in use since Petzold-style Windows programming. MFC uses it extensively. – Grammarian Sep 01 '10 at 23:03
  • @Grammarian I created a follow-up for this question *(I guess, there are some moments which aren't exactly clear)*, could you please take a look - http://stackoverflow.com/questions/10484265/flickering-in-listview-control-ownerdraw-virtual – Yippie-Ki-Yay May 07 '12 at 15:03
4

I get a bunch of

'System.Drawing.NativeMethods' is inaccessible due to its protection level

and

The type name 'NMHDR' does not exist in the type 'System.Drawing.NativeMethods' 

errors. I read somewhere that I have to include user32.dll but can't figure out how to do it in this case.

Edit: OK, I posted before even start to think. I created now my own ListView control and copied the struct from the objectListView code. It seems to work now. Here my code:

public class Listview : ListView
{
    private bool isInWmPaintMsg=false;

    [StructLayout(LayoutKind.Sequential)]
    public struct NMHDR
    {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public int code;
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case 0x0F: // WM_PAINT
                this.isInWmPaintMsg = true;
                base.WndProc(ref m);
                this.isInWmPaintMsg = false;
                break;
            case 0x204E: // WM_REFLECT_NOTIFY
                NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
                if (nmhdr.code == -12)
                { // NM_CUSTOMDRAW
                    if (this.isInWmPaintMsg)
                        base.WndProc(ref m);
                }
                else
                    base.WndProc(ref m);
                break;
            default:
                base.WndProc(ref m);
                break;
        }
    }

}
Jason Plank
  • 2,336
  • 5
  • 31
  • 40
r4i
  • 41
  • 2
2

i can't offer a solution to the ListView calling custom draw events too often, but perhaps you can just mask the problem with double-buffering:

Stackoverflow: How to double buffer .NET controls on a form?

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219