8

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

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

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

4

4 回答 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
}

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);
    }
}
于 2009-08-06T15:46:32.503 回答
2

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

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

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

于 2009-07-15T20:09:22.853 回答
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 Fixed Link上的 WM_SETREDRAW 参考

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

于 2009-08-20T20:58:17.130 回答
0

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

于 2016-10-04T14:00:45.847 回答