【问题标题】:Flickering in listview with ownerdraw and virtualmode使用ownerdraw和virtualmode在listview中闪烁
【发布时间】:2010-10-30 15:32:57
【问题描述】:

我正在使用设置了以下参数的列表视图控件:

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

两行提供了一些随机文本。 Ownerdrawing很简单:

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

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

这是错误吗?如何避免这种行为?

【问题讨论】:

  • 这是一个老问题,但接受的答案不正确,或者至少在 .NET 4.0 中不正确。检查 ListView 类的 DoubleBuffered protected 属性,可能还有我对this 问题的回答。
  • 给出的答案是完全正确的。在 XP 上,如果您有一个虚拟列表并将鼠标悬停在第 0 列上,该控件将闪烁。 DoubleBuffered = true 没有区别。确实,在 Windows 7 上,这个问题不会发生,但这并不会使这个答案不正确。

标签: c# winforms listview


【解决方案1】:

我无法为 ListView 过于频繁地调用自定义绘制事件提供解决方案,但也许您可以通过双缓冲来掩盖问题:

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

【讨论】:

    【解决方案2】:

    这是 .NET 的 ListView 中的一个错误,您无法通过双缓冲来解决它。

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

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

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

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

    1. 覆盖 WndProc
    2. 拦截 WmPaint 消息,并在消息期间设置一个标志为真
    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;
        }
    }
    

    【讨论】:

    • 在我的例子中,我覆盖了每个 OnDrawXXX 函数,并在每个函数前加上一个检查 (this.isInWmPaintMsg == true)。像这种方法一样,它为我解决了这个问题(至少在 Win7 上)。我删除了案例 0x204E,这是一个很好的解决方案,但它依赖于在 0x2000 处定义的 .NET 内部 WM_REFLECT 常量。虽然这几乎可以肯定永远不会改变,但 WM_PAINT 绝对不会改变。在谷歌上找到了这个,非常感谢您的指点!
    • WM_REFLECT_NOTIFY 不是 .NET 特定的。自 Petzold 风格的 Windows 编程以来,它一直在使用。 MFC 广泛使用它。
    • @Grammarian 我为这个问题创建了一个后续问题(我猜,有些时刻不太清楚),请你看看 - @ 987654322@
    【解决方案3】:

    我得到了一堆

    '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 代码中复制了结构。它现在似乎起作用了。这是我的代码:

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

    【讨论】:

      猜你喜欢
      • 2012-05-16
      • 2013-08-04
      • 2013-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-06
      • 2015-03-18
      • 1970-01-01
      相关资源
      最近更新 更多