如何在窗体上双缓冲.NET控件?

59

如何设置受到闪烁影响的窗体控件的受保护的DoubleBuffered属性?

14个回答

94
这里是Dummy's solution的更通用版本。
我们可以使用反射来访问受保护的DoubleBuffered属性,然后将其设置为true注意: 您应该支付开发人员税,不要在用户运行终端服务会话(例如远程桌面)时使用双缓冲。此辅助方法不会在远程桌面上启用双缓冲。
public static void SetDoubleBuffered(System.Windows.Forms.Control c)
{
   //Taxes: Remote Desktop Connection and painting
   //http://blogs.msdn.com/oldnewthing/archive/2006/01/03/508694.aspx
   if (System.Windows.Forms.SystemInformation.TerminalServerSession)
      return;

   System.Reflection.PropertyInfo aProp = 
         typeof(System.Windows.Forms.Control).GetProperty(
               "DoubleBuffered", 
               System.Reflection.BindingFlags.NonPublic | 
               System.Reflection.BindingFlags.Instance);

   aProp.SetValue(c, true, null); 
}

15
这正是你不想要的。在终端会话中,GDI系统可以发送命令(画线、画圆、填充等)。双缓冲是通过将所有内容绘制到位图上,然后使用GDI将整个窗体绘制为位图来完成的。通过网络发送未压缩的位图比发送原始的GDI命令慢得多 - Ian Boyd
3
з”ұдәҺWindowsдёӯзҡ„TEXTBOXжҺ§д»¶дёҚйҒөеҫӘд»»дҪ•з»ҳ画规еҲҷпјҢеӣ жӯӨеҮәзҺ°дәҶиҝҷз§Қжғ…еҶөгҖӮ - Ian Boyd
2
@romkyns,如果您需要一个双缓冲的TextBox,请使用DetectUrls设置为False的RichTextBox。如果您希望它是可编辑的,请使用EM_SETCHARFORMATEM_SETPARAFORMAT消息剥离格式(示例代码在此处)。 - alldayremix
嗯,你的意思是TerminalServerSession是一个异常情况,在运行时应该特别注意吗? - Gregor y
@Gregory 正确。除了尊重用户的字体系列、字体大小、颜色选择、高对比度设置、禁用动画设置外,还要为那些无法使用鼠标的人提供键盘快捷键。你知道的:几乎没有应用程序再做这些事情了。 - Ian Boyd
显示剩余4条评论

72

请查看此帖子,重申其中的核心答案:您可以在窗口上启用WS_EX_COMPOSITED样式标志,以使表单及其所有控件都具有双缓冲功能。该样式标志自XP以来可用。这并不会使绘画速度更快,但整个窗口会在屏幕外缓冲区中绘制,并在一次操作中复制到屏幕上。这使得用户无法看到任何视觉绘制瑕疵而呈现出即时效果。然而,它并非完全没有问题,某些视觉样式渲染器可能会出现故障,特别是当TabControl拥有过多选项卡时。结果因人而异。

将此代码粘贴到您的表单类中:

protected override CreateParams CreateParams {
    get {
        var cp = base.CreateParams;
        cp.ExStyle |= 0x02000000;    // Turn on WS_EX_COMPOSITED
        return cp;
    } 
}
这种技术与Winform的双缓冲支持之间的最大区别在于,Winform的版本仅适用于一个控件。您仍将看到每个单独的控件绘制自己。如果未绘制的控件矩形与窗口背景对比度很大,这可能会看起来像闪烁效果。

这个解决方案会使滚动变慢。 - MatanKri
这个解决方案会导致 ElementHost 中托管的 WPF 控件出现问题,控件将无法正确绘制。 - PeterB
如果您有一个带有分隔器容器的表单,请勿使用WS_EX_COMPOSITED! - Elmue

19
System.Reflection.PropertyInfo aProp = typeof(System.Windows.Forms.Control)
    .GetProperty("DoubleBuffered", System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance);
aProp.SetValue(ListView1, true, null);

Ian在终端服务器上使用这个功能时提供了更多信息。


12
public void EnableDoubleBuffering()
{
   this.SetStyle(ControlStyles.DoubleBuffer | 
      ControlStyles.UserPaint | 
      ControlStyles.AllPaintingInWmPaint,
      true);
   this.UpdateStyles();
}

9

一种方法是扩展您想要双缓冲的特定控件,并在控件的构造函数中设置DoubleBuffered属性。

例如:

class Foo : Panel
{
    public Foo() { DoubleBuffered = true; }
}

5

nobugz 在他的链接中提出了这种方法,我只是转载。将此覆盖添加到表单:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x02000000;
        return cp;
    }
}

对我而言,这是最好的解决方法,在Windows 7上,当我调整一个控件密集型的表单时,会出现大块黑色区域。现在,控件会反弹!但是这样更好。


5

扩展方法:用于控制缓冲开关的双重缓冲

public static class ControlExtentions
{
    /// <summary>
    /// Turn on or off control double buffering (Dirty hack!)
    /// </summary>
    /// <param name="control">Control to operate</param>
    /// <param name="setting">true to turn on double buffering</param>
    public static void MakeDoubleBuffered(this Control control, bool setting)
    {
        Type controlType = control.GetType();
        PropertyInfo pi = controlType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
        pi.SetValue(control, setting, null);
    }
}

使用方法(例如如何使DataGridView双缓冲):

DataGridView _grid = new DataGridView();
//  ...
_grid.MakeDoubleBuffered(true);

5

这是一个优秀解决方案的VB.NET版本....:

Protected Overrides ReadOnly Property CreateParams() As CreateParams
    Get
        Dim cp As CreateParams = MyBase.CreateParams
        cp.ExStyle = cp.ExStyle Or &H2000000
        Return cp
    End Get
End Property

4

在尝试双缓冲之前,请尝试使用SuspendLayout()/ResumeLayout()解决您的问题。


3
使用Suspend/ResumeLayout不能解决绘制时出现闪烁的问题。 - Ian Boyd

4
这让我在两天内与第三方控件发生了很多烦恼,直到我找到问题所在。
protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x02000000;
        return cp;
    }
}

最近我调整/重绘一个包含多个控件的控件时,出现了很多空洞(残留物)。

我尝试过WS_EX_COMPOSITED和WM_SETREDRAW,但是直到我使用了以下方法才解决了问题:

private void myPanel_SizeChanged(object sender, EventArgs e)
{
     Application.DoEvents();
}

我想转达一下。


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