使用ownerdraw和virtualmode在listview中闪烁

我正在使用listview控件并设置以下参数:

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); 

两行提供一些随机文本。 拥有者绘图很简单:

  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); } 

问题是:当我将鼠标hover在listview的内容上时,我会看到第一列的闪烁。 调试显示DrawSubItem在鼠标hover时不断调用。

是bug吗? 如何避免这种行为?

这是.NET的ListView中的一个错误,你不能通过双缓冲绕过它。

在虚拟列表上,当鼠标hover在第0列上时,基础控件会生成大量自定义绘制事件。即使启用DoubleBuffering,这些自定义绘制事件也会导致闪烁,因为它们是在正常的WmPaint消息之外发送的。

我似乎也记得这只发生在XP上。 Vista修复了这个(但引入了其他人)。

您可以查看ObjectListView中的代码,看看它是如何解决这个问题的。

如果你想自己解决它,你需要深入研究ListView控件的内部管道:

  1. 覆盖WndProc
  2. 拦截WmPaint消息,并设置一个在msg期间为真的标志
  3. 拦截WmCustomDraw消息,并忽略WmPaint事件之外发生的所有消息。

像这样的东西::

 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; } } 

我得到了一堆

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

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

错误。 我在某处读到了我必须包含user32.dll但在这种情况下无法弄清楚如何做到这一点。

编辑:好的,我发布之前甚至开始思考。 我现在创建了自己的ListView控件,并从objectListView代码中复制了struct。 它现在似乎工作。 这是我的代码:

 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; } } } 

我不能经常为ListView调用自定义绘制事件提供解决方案,但也许您可以通过双缓冲来掩盖问题:

Stackoverflow:如何在表单上双重缓冲.NET控件?