11

This question might be considered a follow-up to Flickering in listview with ownerdraw and virtualmode.

I've got a ListView control in Virtual mode and I attempt to perform custom drawing. Item rendering is done via the following method override:

protected override void OnDrawItem(DrawListViewItemEventArgs eventArgs) 

As mentioned in the referenced question, custom drawing introduces flickering on mouse over events. Debugger tells me this happens due to an excessive amount of custom draw events which are fired.


Now - the accepted answer to the referenced question tells us:

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

  • So, how reliable is that information? Is that really a bug? Or maybe we simply attempt to cut off a part of the messages and hope that it won't alter the visible behavior?

  • Is this true that if I have my owner drawing routine for the ListView in Virtual Mode, I can suppress these Custom Draw events and only perform my drawing in WM_PAINT or, maybe, this is incorrect for some cases?

  • What are the prerequisities for the System.Windows.Forms control to be able to do all the painting in WM_PAINT without altering it's initial behavior?

Community
  • 1
  • 1
Yippie-Ki-Yay
  • 22,026
  • 26
  • 90
  • 148
  • The MSDN Sample is crap. There are several bugs inside it. I have solved them by overriding WndProc and filtering specific NMHDR messages out which have to do with hot tracking and other stuff. I do not have the code here right now but I can provide a sample which solves the issues you also did experience. – Alois Kraus May 13 '12 at 06:04

3 Answers3

11

At least for double buffering for OnDrawItem, it is incorrect that there is a bug, but it is a little bit stupid: there is a protected attribute you can set, but you need to override the ListView. I created this kind of class:

public class MyListView : ListView
{
    public MyListView()
        : base()
    {
        DoubleBuffered = true;
    }
}

And then in my MyForm.Designer.cs file I change the instantiation of the ListView with the following line:

private ListView myListView;

this.myListView = new MyListView();

And OnDrawItem will work like a charm!

zmilojko
  • 2,125
  • 17
  • 27
  • 1
    Setting `DoubleBuffered` attribute does not solve the mouse-over-first-column flickering, because the painting in this case actually happens outside the `WM_PAINT` processing. While digging into the code, I found that sent drawing events are actually "fake" and they are only used to determine the size of the label (sic!). Well, at least it seems to be so (I was debugging the `user32.dll` library with symbols downloaded). – Yippie-Ki-Yay May 08 '12 at 23:39
  • 1
    Also to mention, I know at least two ways to solve this problem, however, my question was mostly about the reliability of the all-in-`WM_PAINT` approach. **Anyway, thank you for you efforts.** – Yippie-Ki-Yay May 08 '12 at 23:42
  • I was suffering from flickering ListView instances even when updating a set of three items that hadn't changed once a second. I was using BeginUpdate and EndUpdate to no avail, but that coupled with this extended class has solved the problem instantly. Many thanks! – LordWilmore Aug 18 '16 at 08:56
  • Doing this upset the Visual Studio designer, because it couldn't find the tweaked class. I found that getting the property through System.Reflection and setting it directly worked. I added a button to toggle back and forth between owner-draw and system-draw to compare the two, which got a little weird with multi-select, so maybe it's best to just set it during init and leave it alone. – fadden May 09 '18 at 21:08
4

Like This Answer in Here, though not sure but,

I think that ListView.BeginUpdate() and ListView.EndUpdate() will solve the problem.

MSDN Thread about This

Maybe In This Way :

protected override void OnDrawItem(DrawListViewItemEventArgs eventArgs)
{
    ListView.BeginUpdate();
    //Do Works Here
    ListView.EndUpdate();
}

Update

Another Alternative may be using a new Thread in BackgroundWorker to Update the ListView... I implemented this along with BeginUpdate()/EndUpDate() in my application and found it relatively faster than only the BeginUpdate()/EndUpDate()..

Update

I found another working Solution at SO, a Helper class provided by Brian Gillespie :

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);
    }
}
Community
  • 1
  • 1
Writwick
  • 2,133
  • 6
  • 23
  • 54
  • For some reason in my application this spikes the `CPU` usage up to 100% (for the current core). – Yippie-Ki-Yay May 16 '12 at 09:24
  • Does your Code constantly tries to Redraw the ListView – Writwick May 16 '12 at 09:53
  • about 6 years later and `Listview` still flickers and `BeginUpdate` and `EndUpdate` still has no effect. however `EnableDoubleBuffer` suggested here works. I called it in my `formLoad` and `DisableDoubleBuffer` is not necessary. Also to make the code even smaller, you don't need the `ListViewExtendedStyles` enum, just get `DoubleBuffer = 0x00010000` value. – AaA Aug 03 '18 at 04:16
4

I've seen this flickering issue with ListView control in any custom rendering event handlers (DrawItem, DrawSubItem). I tried BeginUpdate()/EndUpdate() and double buffering with no success. I think .NET triggers additional WM_PAINT to all columns to the right of the custom drawn column.

However I found this workaround to a single custom rendered column ListView. It works just fine!

  1. In the columnheader editor, set the custom drawn column as the last column
  2. Change the "DisplayIndex" of the position you want

This should solve the flickering in mouseover or run-time rendering.

detale
  • 12,482
  • 4
  • 41
  • 42