双缓冲列表框

9
我有一个 CheckedListBox (WinForms) 控件(继承自 ListBox;谷歌搜索显示问题出现在 ListBox 上),它被锚定到其窗体的四个边缘。当窗体被调整大小时,ListBox 会出现丑陋的闪烁。我尝试继承 CheckedListBox 并在构造函数中将 DoubleBuffered 设置为 true(这种技术适用于其他控件,包括 ListView 和 DataGridView),但没有效果。
我尝试向 CreateParams 添加 WS_EX_COMPOSITED 样式,这有所帮助,但使窗体调整大小变得更加缓慢。
是否有其他方法可以防止这种闪烁?
4个回答

13

我遇到了类似的问题,尽管是在一个自绘的列表框上。我的解决方案是使用BufferedGraphics对象。如果你的列表框不是自绘的,你的情况可能会有所不同,但也许这个解决方案会给你一些启示。

我发现,如果我没有提供TextFormatFlags.PreserveGraphicsTranslateTransform参数,TextRenderer很难渲染到正确的位置。另一种方法是使用P/Invoke调用BitBlt来直接在图形上下文之间复制像素。我选择了这种方式,因为这是两种恶中较轻的一种。

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

frenchtoast建议的GDI类。

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

1
@Eric:你从哪里获取GDI?它是一个参考吗?例如,我尝试添加Graphics GDI = this.CreateGraphics();但它没有CopyGraphics方法。或者你之前导入了Gdi32.dll吗? - Matt
@Eric:谢谢您的提示,OnDrawItem 是在我改变了 DrawMode 之后被调用的。现在我可以看到我在 BitBlt 方面有一些问题,因为 ListBox 窗口完全是空的 - 我必须解决这个问题。 - Matt
3
EricпјҢиҜ·й—®дҪ иғҪеҗҰеҸ‘еёғGDI.CopyGraphicsзҡ„еҢ…иЈ…ж–№жі•пјҹ - Jeremy Thompson
@JeremyThompson:它并不复杂,但需要相当多的代码。在其核心部分,它是围绕BitBlt GDI方法的一个包装器,使用了几个结构体和枚举类型。您可以查看pinvoke.net以获取有关如何包装它的一些详细信息。 - Eric
1
@JeremyThompson,我在谷歌上搜索了GDI.CopyGraphics,并在这里找到了它(https://github.com/idunnololz/DivisionByZeroLevelEditor/blob/master/GeneralControlLibrary/GDI.cs)。 - chinookf
显示剩余6条评论

3
你可以尝试使用带有复选框的ListView控件来改善情况。它不太容易处理(但是,WinForms ListBox也不是一招妙计),我发现使用DoubleBuffered=true时,它的调整行为是可以忍受的。
或者,你可以通过覆盖父窗体的背景绘制来减少闪烁-提供空心刷子或通过什么都不做并返回TRUE来覆盖WM_ERASEBKND。(如果你的控件覆盖父窗体的整个客户端区域,那么这样做就可以了,否则你需要更复杂的背景绘制方法。
我已经在Win32应用程序中成功使用过这种方法,但我不知道Forms控件是否添加了一些自己的魔力,使其无法正常工作。

1
这是一个私有属性,但可以使用反射进行设置: typeof(ListBox).GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(myListBox, true); - Louis Somers

0

以前通过向控件发送WM_SETREDRAW消息来处理此问题。

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

另请参阅:Microsoft的WM_SETREDRAW参考 已修复链接

如果有其他人在.NET下使用过Windows消息,请根据需要更新此帖子。


闪烁是由于调整大小引起的,因此这不是最佳解决方案。然而,我可能仍会这样做。 - SLaks
这并没有解决我的ListBox闪烁问题。 - AlainD
这个答案是错误的,因为当窗体调整大小时,你的代码甚至没有被执行。 - Elmue

0

虽然没有解决闪烁的具体问题,但是对于这种类型的问题通常有效的方法是缓存 ListBox 项的最小状态。然后对每个项目执行一些计算来确定是否需要重绘 ListBox。仅当至少一个项目需要更新时才更新 ListBox(当然要将此新状态保存在缓存中以供下一个周期使用)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接