双缓冲ListBox

我有一个CheckedListBox(WinForms)控件(它inheritance自ListBox; googling显示问题在于ListBox),它被锚定到其表单的所有四个边。 调整窗体大小时,ListBox有一个丑陋的闪烁。 我尝试inheritanceCheckedListBox并在ctor DoubleBuffered设置为true (此技术适用于其他控件,包括ListView和DataGridView),但它没有任何效果。

我尝试将WS_EX_COMPOSITED样式添加到CreateParams ,这有所帮助,但使表单resize更慢。

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

虽然有一个所有者绘制的列表框,但我遇到了类似的问题。 我的解决方案是使用BufferedGraphics对象。 如果您的列表不是所有者绘制的,那么您的里程可能因此解决方案而异,但也许它会给您一些灵感。

我发现TextRenderer很难渲染到正确的位置,除非我提供了TextFormatFlags.PreserveGraphicsTranslateTransform。 替代方法是使用P / Invoke调用BitBlt直接在图形上下文之间复制像素。 我选择这个作为两个邪恶的较小者。

 ///  /// This class is a double-buffered ListBox for owner drawing. /// The double-buffering is accomplished by creating a custom, /// off-screen buffer during painting. ///  public sealed class DoubleBufferedListBox : ListBox { #region Method Overrides ///  /// Override OnTemplateListDrawItem to supply an off-screen buffer to event /// handlers. ///  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, pX, pY, SRCCOPY); g.ReleaseHdc(hdc1); bufferedGraphics.ReleaseHdc(hdc2); } } 

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

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

我已经在Win32应用程序中成功使用了这个,但是我不知道Forms控件是否会添加一些自己的魔法来使这个function失效。

这通常是通过向控件发送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 Fixed Link上的 WM_SETREDRAW参考

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

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