使用ownerdraw和virtualmode在listview中闪烁

sil*_*_dg 2 c# listview winforms

我正在使用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);
Run Code Online (Sandbox Code Playgroud)

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

    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);
    }
Run Code Online (Sandbox Code Playgroud)

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

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

Gra*_*ian 5

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

在虚拟列表上,当鼠标悬停在第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;
    }
}
Run Code Online (Sandbox Code Playgroud)