ListView 控件闪烁(OwnerDraw、Virtual)

Posted

技术标签:

【中文标题】ListView 控件闪烁(OwnerDraw、Virtual)【英文标题】:Flickering in ListView control (OwnerDraw, Virtual) 【发布时间】:2012-05-16 02:09:19 【问题描述】:

这个问题可能被认为是 Flickering in listview with ownerdraw and virtualmode 的后续问题。

我在Virtual mode 中有一个ListView 控件,我尝试执行自定义绘图。项目渲染是通过以下方法覆盖完成的:

protected override void OnDrawItem(DrawListViewItemEventArgs eventArgs) 

如引用问题中所述,自定义绘图会在鼠标悬停事件时引入闪烁。调试器告诉我这是由于触发了过多的自定义绘制事件。


现在 - 引用问题的公认答案告诉我们:

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

那么,这些信息有多可靠?这真的是一个 错误吗? 或者我们只是尝试截断部分消息并希望它不会改变可见的行为?

如果我在Virtual Mode, 中有我的所有者为ListView 绘制例程,我可以抑制这些Custom Draw 事件并且只在WM_PAINT 中执行我的绘制,这是真的吗,或者,也许这是不正确的对于某些情况?

System.Windows.Forms 控件能够在不改变其初始行为的情况下完成 WM_PAINT 中的所有绘制的先决条件是什么?

【问题讨论】:

MSDN 示例很垃圾。它里面有几个错误。我已经通过覆盖 WndProc 并过滤掉与热跟踪和其他东西有关的特定 NMHDR 消息来解决它们。我现在没有代码,但我可以提供一个示例来解决您也遇到的问题。 【参考方案1】:

至少对于OnDrawItem的双缓冲,有bug是不对的,但是有点傻:有一个protected属性可以设置,但是需要重写ListView。我创建了这种类:

public class MyListView : ListView

    public MyListView()
        : base()
    
        DoubleBuffered = true;
    

然后在我的 MyForm.Designer.cs 文件中,我使用以下行更改 ListView 的实例化:

private ListView myListView;

this.myListView = new MyListView();

OnDrawItem 会像魅力一样工作!

【讨论】:

设置DoubleBuffered属性并不能解决鼠标悬停在第一列的闪烁,因为这种情况下的绘制实际上发生在WM_PAINT处理之外。在深入研究代码时,我发现发送的绘图事件实际上是“假的”,它们仅用于确定标签的大小(原文如此!)。好吧,至少看起来是这样(我正在调试 user32.dll 库并下载了符号)。 另外提一下,我知道至少有两种方法可以解决这个问题,但是,我的问题主要是关于 all-in-WM_PAINT 方法的可靠性。 无论如何,谢谢你的努力。 即使在更新一组三个未更改的项目时,我也遇到闪烁的 ListView 实例。我使用 BeginUpdate 和 EndUpdate 无济于事,但是加上这个扩展类已经立即解决了这个问题。非常感谢! 这样做会让 Visual Studio 设计者感到不安,因为它找不到经过调整的类。我发现通过 System.Reflection 获取属性并直接设置它是有效的。我添加了一个按钮来在 owner-draw 和 system-draw 之间来回切换以比较两者,这在多选时有点奇怪,所以也许最好在 init 期间设置它并不管它。【参考方案2】:

我在任何自定义呈现事件处理程序(DrawItem、DrawSubItem)中都看到了 ListView 控件的这种闪烁问题。我尝试了 BeginUpdate()/EndUpdate() 和双缓冲但没有成功。我认为 .NET 会为 自定义绘制列右侧的所有列触发额外的 WM_PAINT。

但是,我发现此解决方法适用于单个自定义呈现的列 ListView。效果很好!

    在列标题编辑器中,将自定义绘制的列设置为最后一列 更改所需位置的“DisplayIndex”

这应该可以解决鼠标悬停或运行时渲染中的闪烁问题。

【讨论】:

【参考方案3】:

喜欢This Answer in Here,虽然不确定,

我认为ListView.BeginUpdate()ListView.EndUpdate() 会解决这个问题。

MSDN Thread about This

也许是这样:

protected override void OnDrawItem(DrawListViewItemEventArgs eventArgs)

    ListView.BeginUpdate();
    //Do Works Here
    ListView.EndUpdate();

更新

另一种选择可能是在 BackgroundWorker 中使用新线程来更新 ListView... 我在我的应用程序中与BeginUpdate()/EndUpDate() 一起实现了这个,发现它比仅BeginUpdate()/EndUpDate()..

更新

我找到了另一个工作的Solution at SO,一个由Brian Gillespie 提供的Helper 类:

public enum ListViewExtendedStyles

    /// <summary>
    /// LVS_EX_GRIDLINES
    /// </summary>
    GridLines = 0x00000001,
    /// <summary>
    /// LVS_EX_SUBITEMIMAGES
    /// </summary>
    SubItemImages = 0x00000002,
    /// <summary>
    /// LVS_EX_CHECKBOXES
    /// </summary>
    CheckBoxes = 0x00000004,
    /// <summary>
    /// LVS_EX_TRACKSELECT
    /// </summary>
    TrackSelect = 0x00000008,
    /// <summary>
    /// LVS_EX_HEADERDRAGDROP
    /// </summary>
    HeaderDragDrop = 0x00000010,
    /// <summary>
    /// LVS_EX_FULLROWSELECT
    /// </summary>
    FullRowSelect = 0x00000020,
    /// <summary>
    /// LVS_EX_ONECLICKACTIVATE
    /// </summary>
    OneClickActivate = 0x00000040,
    /// <summary>
    /// LVS_EX_TWOCLICKACTIVATE
    /// </summary>
    TwoClickActivate = 0x00000080,
    /// <summary>
    /// LVS_EX_FLATSB
    /// </summary>
    FlatsB = 0x00000100,
    /// <summary>
    /// LVS_EX_REGIONAL
    /// </summary>
    Regional = 0x00000200,
    /// <summary>
    /// LVS_EX_INFOTIP
    /// </summary>
    InfoTip = 0x00000400,
    /// <summary>
    /// LVS_EX_UNDERLINEHOT
    /// </summary>
    UnderlineHot = 0x00000800,
    /// <summary>
    /// LVS_EX_UNDERLINECOLD
    /// </summary>
    UnderlineCold = 0x00001000,
    /// <summary>
    /// LVS_EX_MULTIWORKAREAS
    /// </summary>
    MultilWorkAreas = 0x00002000,
    /// <summary>
    /// LVS_EX_LABELTIP
    /// </summary>
    LabelTip = 0x00004000,
    /// <summary>
    /// LVS_EX_BORDERSELECT
    /// </summary>
    BorderSelect = 0x00008000,
    /// <summary>
    /// LVS_EX_DOUBLEBUFFER
    /// </summary>
    DoubleBuffer = 0x00010000,
    /// <summary>
    /// LVS_EX_HIDELABELS
    /// </summary>
    HideLabels = 0x00020000,
    /// <summary>
    /// LVS_EX_SINGLEROW
    /// </summary>
    SingleRow = 0x00040000,
    /// <summary>
    /// LVS_EX_SNAPTOGRID
    /// </summary>
    SnapToGrid = 0x00080000,
    /// <summary>
    /// LVS_EX_SIMPLESELECT
    /// </summary>
    SimpleSelect = 0x00100000


public enum ListViewMessages

    First = 0x1000,
    SetExtendedStyle = (First + 54),
    GetExtendedStyle = (First + 55),


/// <summary>
/// Contains helper methods to change extended styles on ListView, including enabling double buffering.
/// Based on Giovanni Montrone's article on <see cref="http://www.codeproject.com/KB/list/listviewxp.aspx"/>
/// </summary>
public class ListViewHelper

    private ListViewHelper()
    
    

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern int SendMessage(IntPtr handle, int messg, int wparam, int lparam);

    public static void SetExtendedStyle(Control control, ListViewExtendedStyles exStyle)
    
        ListViewExtendedStyles styles;
        styles = (ListViewExtendedStyles)SendMessage(control.Handle, (int)ListViewMessages.GetExtendedStyle, 0, 0);
        styles |= exStyle;
        SendMessage(control.Handle, (int)ListViewMessages.SetExtendedStyle, 0, (int)styles);
    

    public static void EnableDoubleBuffer(Control control)
    
        ListViewExtendedStyles styles;
        // read current style
        styles = (ListViewExtendedStyles)SendMessage(control.Handle, (int)ListViewMessages.GetExtendedStyle, 0, 0);
        // enable double buffer and border select
        styles |= ListViewExtendedStyles.DoubleBuffer | ListViewExtendedStyles.BorderSelect;
        // write new style
        SendMessage(control.Handle, (int)ListViewMessages.SetExtendedStyle, 0, (int)styles);
    
    public static void DisableDoubleBuffer(Control control)
    
        ListViewExtendedStyles styles;
        // read current style
        styles = (ListViewExtendedStyles)SendMessage(control.Handle, (int)ListViewMessages.GetExtendedStyle, 0, 0);
        // disable double buffer and border select
        styles -= styles & ListViewExtendedStyles.DoubleBuffer;
        styles -= styles & ListViewExtendedStyles.BorderSelect;
        // write new style
        SendMessage(control.Handle, (int)ListViewMessages.SetExtendedStyle, 0, (int)styles);
    

【讨论】:

由于某种原因,在我的应用程序中,CPU 的使用率高达 100%(对于当前核心)。 您的代码是否不断尝试重绘 ListView 大约 6 年后,Listview 仍然闪烁,BeginUpdateEndUpdate 仍然没有效果。但是这里建议的EnableDoubleBuffer 有效。我在我的formLoad 中调用了它,而DisableDoubleBuffer 没有必要。另外为了使代码更小,您不需要ListViewExtendedStyles 枚举,只需获取DoubleBuffer = 0x00010000 值。

以上是关于ListView 控件闪烁(OwnerDraw、Virtual)的主要内容,如果未能解决你的问题,请参考以下文章

避免在调整大小时移动其控件的对话框上闪烁

C#使用ListView更新数据出现闪烁解决办法

用C#listview控件Details类型,发现当拉动表头调整列宽度过程时,listview会重画,造成不停的闪烁

C# Winform开发中,通过timer控件对Listview中的数据进行刷新,存在不停闪烁的问题!

Android中ListView异步加载图片错位重复闪烁问题分析及解决方案

使用 SS_OWNERDRAW 动态创建 CStatic 时程序在 UpdateWindow 上崩溃