9

I have a CheckedListBox (WinForms) control (which inherits from ListBox; googling shows that the problem is with ListBox) that is anchored to all four sides of its form. When the form is resized, the ListBox has an ugly flicker. I tried inheriting CheckedListBox and setting DoubleBuffered to true in the ctor (this technique works with other controls, including ListView and DataGridView), but it had no effect.

I tried adding the WS_EX_COMPOSITED style to CreateParams, and this helped, but makes the form resize mush more slowly.

Is there any other way to prevent this flickering?

Julien Poulin
  • 12,737
  • 10
  • 51
  • 76
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964

4 Answers4

13

I was having similar issues albeit with an owner drawn listbox. My solution was to use BufferedGraphics objects. Your mileage may vary with this solution if your list isn't owner drawn, but maybe it will give you some inspiration.

I found that TextRenderer had difficulties rendering to the correct location unless I suppled TextFormatFlags.PreserveGraphicsTranslateTransform. The alternative to this was to use P/Invoke to call BitBlt to directly copy pixels between the graphics contexts. I chose this as the lesser of two evils.

/// <summary>
/// This class is a double-buffered ListBox for owner drawing.
/// The double-buffering is accomplished by creating a custom,
/// off-screen buffer during painting.
/// </summary>
public sealed class DoubleBufferedListBox : ListBox
{
    #region Method Overrides
    /// <summary>
    /// Override OnTemplateListDrawItem to supply an off-screen buffer to event
    /// handlers.
    /// </summary>
    protected override void OnDrawItem(DrawItemEventArgs e)
    {
        BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;

        Rectangle newBounds = new Rectangle(0, 0, e.Bounds.Width, e.Bounds.Height);
        using (BufferedGraphics bufferedGraphics = currentContext.Allocate(e.Graphics, newBounds))
        {
            DrawItemEventArgs newArgs = new DrawItemEventArgs(
                bufferedGraphics.Graphics, e.Font, newBounds, e.Index, e.State, e.ForeColor, e.BackColor);

            // Supply the real OnTemplateListDrawItem with the off-screen graphics context
            base.OnDrawItem(newArgs);

            // Wrapper around BitBlt
            GDI.CopyGraphics(e.Graphics, e.Bounds, bufferedGraphics.Graphics, new Point(0, 0));
        }
    }
    #endregion
}

The GDI class (suggested by frenchtoast).

public static class GDI
{
    private const UInt32 SRCCOPY = 0x00CC0020;

    [DllImport("gdi32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, UInt32 dwRop);

    public static void CopyGraphics(Graphics g, Rectangle bounds, Graphics bufferedGraphics, Point p)
    {
        IntPtr hdc1 = g.GetHdc();
        IntPtr hdc2 = bufferedGraphics.GetHdc();

        BitBlt(hdc1, bounds.X, bounds.Y, 
            bounds.Width, bounds.Height, hdc2, p.X, p.Y, SRCCOPY);

        g.ReleaseHdc(hdc1);
        bufferedGraphics.ReleaseHdc(hdc2);
    }
}
D.Kastier
  • 2,640
  • 3
  • 25
  • 40
Eric
  • 6,364
  • 1
  • 32
  • 49
  • 1
    @Eric: Where do you get GDI from? Is it a reference? For example, I've tried to add `Graphics GDI = this.CreateGraphics();` but it doesn't have the CopyGraphics method. Or did you import Gdi32.dll previously? – Matt Jan 04 '13 at 17:49
  • Ok - working now. I've added `GDI32.dll` with `BitBlt` method, wrapped it as `GDI.CopyGraphics(...)` and now it works ... the only thing is that is flickering just the same as the original ListBox. Any ideas how to fix it? – Matt Jan 04 '13 at 18:58
  • @Matt: Is the ListBox set to be OwnerDrawn? – Eric Jan 05 '13 at 07:43
  • @Eric - are you referring to this MSDN article: [How to create an OwnerDrawn Listbox](http://msdn.microsoft.com/en-us/library/ms229679(v=vs.90).aspx)? This creates a listbox derived from the `Control` class rather than from the `ListBox` class, which needs more code. – Matt Jan 05 '13 at 09:50
  • @Matt: You need to set the DrawStyle property on the ListBox to OwnerDrawn. – Eric Jan 05 '13 at 19:28
  • @Eric: Thanks for the hint, `OnDrawItem` is called after I've changed the DrawMode. I can see now that I have some issue with the BitBlt, since the ListBox window is entirely empty - will have to sort this out. – Matt Jan 08 '13 at 07:05
  • @Matt: The window is empty because with an OwnerDrawn ListBox, you have to draw the content (text and/or graphics) for each item in the ListBox by handling the DrawItem event. – Eric Jan 08 '13 at 19:25
  • @Eric: I've added `private void listBoxOutput_DrawItem(object sender, DrawItemEventArgs e) { var lb = (ListBox)sender; var item = lb.Items[e.Index].ToString(); var brush = Brushes.Black; e.Graphics.DrawString(item, lb.Font, brush, e.Bounds); }` which works in a normal ListBox with `DrawMode = OwnerDrawFixed`, with the DoubleBufferedListbox, only the vertical scrollbar is updated, the textarea remains blank. A breakpoint shows that the code is executed. – Matt Jan 09 '13 at 22:13
  • 3
    Eric, could you please post the `GDI.CopyGraphics` wrapper method? – Jeremy Thompson Dec 16 '13 at 03:13
  • @JeremyThompson: It's not complex but requires quite a bit of code. At its core, it is a wrapper around the BitBlt GDI method, which uses several structs and enums. You can check pinvoke.net for some details on how to wrap that. – Eric Dec 17 '13 at 14:31
  • 1
    @JeremyThompson, I googled GDI.CopyGraphics, and found it [here](https://github.com/idunnololz/DivisionByZeroLevelEditor/blob/master/GeneralControlLibrary/GDI.cs) – chinookf Jun 12 '17 at 19:12
3

You could check if switching to a ListView Control with checkboxes improves matters. It's not as easy to deal with (but hey, the WinForms ListBox isn't a stroke of genius either), I found that it's resize behavior with DoubleBuffered=true is bearable.

Alternatively, you could try to reduce flicker by overriding the parent forms background drawing - either providing a hollow brush, or overriding WM_ERASEBKND by doing nothing and returning TRUE. (that's ok if your control covers the entire client area of the parent form, otherwise you'd need a more complex background drawing method.

I've used this successfully in Win32 applications, but I don't know if the Forms control adds some of it's own magic that renders this nonfunctional.

EricLaw
  • 56,563
  • 7
  • 151
  • 196
peterchen
  • 40,917
  • 20
  • 104
  • 186
  • It's a private property, but it can be set using reflection: `typeof(ListBox).GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(myListBox, true);` – Louis Somers Apr 01 '22 at 10:06
0

This used to be handled by sending the WM_SETREDRAW message to the control.

const int WM_SETREDRAW = 0x0b;

Message m = Message.Create(yourlistbox.Handle, WM_SETREDRAW, (IntPtr) 0, (IntPtr) 0);
yourform.DefWndProc(ref m);

// do your updating or whatever else causes the flicker

Message m = Message.Create(yourlistbox.Handle, WM_SETREDRAW, (IntPtr) 1, (IntPtr) 0);
yourform.DefWndProc(ref m);

See also: WM_SETREDRAW reference at Microsoft Fixed Link

If anyone else has used windows messages under .NET, please update this posting as necessary.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
Chris Judge
  • 1,952
  • 1
  • 13
  • 10
0

Although not addressing the specific issue of flickering, a method that is frequently effective for this type of issue is to cache a minimal state of the ListBox items. Then determine whether you need to redraw the ListBox by performing some calculation on each item. Only update the ListBox if at least one item needs to be updated (and of course save this new state in the cache for the next cycle).

AlainD
  • 5,413
  • 6
  • 45
  • 99