【问题标题】:Double buffered ListBox双缓冲列表框
【发布时间】:2010-11-11 00:59:19
【问题描述】:

我有一个 CheckedListBox (WinForms) 控件(它继承自 ListBox;谷歌搜索显示问题出在 ListBox 上),该控件锚定到其表单的所有四个侧面。调整窗体大小时,ListBox 会出现难看的闪烁。我尝试在ctor中继承CheckedListBox并将DoubleBuffered设置为true(此技术适用于其他控件,包括ListView和DataGridView),但没有效果。

我尝试将WS_EX_COMPOSITED 样式添加到CreateParams,这有所帮助,但会使表单调整大小变得更慢。

还有其他方法可以防止这种闪烁吗?

【问题讨论】:

    标签: c# .net winforms listbox doublebuffered


    【解决方案1】:

    尽管使用所有者绘制的列表框,但我也遇到了类似的问题。我的解决方案是使用 BufferedGraphics 对象。如果您的列表不是所有者绘制的,那么此解决方案可能会导致您的里程数有所不同,但也许它会给您一些启发。

    我发现 TextRenderer 难以渲染到正确的位置,除非我提供了 TextFormatFlags.PreserveGraphicsTranslateTransform。对此的替代方法是使用 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
    }
    

    GDI 类(由 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);
        }
    }
    

    【讨论】:

    • @Eric:你从哪里获得 GDI?是参考吗?例如,我尝试添加 Graphics GDI = this.CreateGraphics();,但它没有 CopyGraphics 方法。还是您之前导入了 Gdi32.dll?
    • 好的 - 现在工作。我用BitBlt 方法添加了GDI32.dll,将其包装为GDI.CopyGraphics(...),现在它可以工作了……唯一的事情是闪烁与原始ListBox 相同。任何想法如何解决它?
    • @Matt: ListBox 设置为 OwnerDrawn 了吗?
    • Eric,能否请您发布GDI.CopyGraphics 包装方法?
    • @JeremyThompson,我用谷歌搜索了 GDI.CopyGraphics,找到了here
    【解决方案2】:

    您可以检查切换到带有复选框的 ListView 控件是否会改善问题。它不是那么容易处理(但是,嘿,WinForms ListBox 也不是天才),我发现它使用DoubleBuffered=true 调整大小的行为是可以忍受的。

    或者,您可以尝试通过覆盖父窗体背景绘图来减少闪烁 - 要么提供一个空心画笔,要么通过不执行任何操作并返回 TRUE 来覆盖 WM_ERASEBKND。 (如果你的控件覆盖了父窗体的整个客户区就可以了,否则你需要一个更复杂的背景绘制方法。

    我已经在 Win32 应用程序中成功地使用了它,但我不知道 Forms 控件是否添加了一些它自己的魔力,使其无法正常工作。

    【讨论】:

      【解决方案3】:

      这过去是通过向控件发送 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);
      

      另见:WM_SETREDRAW reference at Microsoft 固定链接

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

      【讨论】:

      • 闪烁是由resize引起的,所以这不是最佳解决方案。但是,无论如何我都可以这样做。
      • 这并不能解决我的 ListBox 上的闪烁问题。
      • 这个答案是错误的,因为你的代码甚至在窗体调整大小时都没有执行。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2012-10-23
        • 1970-01-01
        • 1970-01-01
        • 2013-08-14
        • 2011-03-05
        • 2011-08-20
        • 2011-04-23
        • 2011-10-16
        • 2012-01-19
        相关资源
        最近更新 更多