如何在表单上加倍缓冲 .NET 控件?

Posted

技术标签:

【中文标题】如何在表单上加倍缓冲 .NET 控件?【英文标题】:How to double buffer .NET controls on a form? 【发布时间】:2010-09-09 18:31:12 【问题描述】:

如何在出现闪烁的表单上设置受保护的DoubleBuffered 属性?

【问题讨论】:

【参考方案1】:

这是Dummy's solution 的更通用版本。

我们可以使用反射来获取受保护的DoubleBuffered属性,然后将其设置为true

注意:你应该pay your developer taxes而不是use double-buffering if the user is running in a terminal services session(例如远程桌面)如果这个人在远程桌面上运行,这个帮助方法不会打开双缓冲。

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

【讨论】:

有趣的是,我认为在远程处理时双缓冲更重要,这样您就可以避免通过网络发送一堆不必要的重绘? 这正是您不想要的。在终端会话中,GDI 系统可以发送命令(在此处画线,在此处画圆,在此处填充等)。双缓冲是通过将所有内容绘制到位图上,然后使用 GDI 将整个表单绘制为位图来完成的。通过网络发送未压缩的位图比发送原始 GDI 命令慢很多 无助于防止自动调整大小的文本框在调整大小时闪烁......事实上,到目前为止我没有尝试过。 @Boris 那是因为 Windows TEXTBOX 控制 doesn't obey any 的绘画法则。 @romkyns,如果您需要双缓冲 TextBox,请使用 RichTextBox 并将 DetectUrls 设置为 False。如果您希望它可编辑,请使用EM_SETCHARFORMAT 和EM_SETPARAFORMAT 消息(示例代码is here)去除格式。【参考方案2】:

查看this thread

重复该答案的核心,您可以打开窗口上的 WS_EX_COMPOSITED 样式标志,以使表单及其所有控件都双缓冲。样式标志从 XP 开始可用。它不会使绘制速度更快,但整个窗口都被绘制在屏幕外缓冲区中,并一次被传送到屏幕上。使其在用户眼中看起来是即时的,没有可见的绘画伪影。它并非完全没有问题,一些视觉样式渲染器可能会出现故障,尤其是 TabControl 选项卡太多时。 YMMV。

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

protected override CreateParams CreateParams 
    get 
        var cp = base.CreateParams;
        cp.ExStyle |= 0x02000000;    // Turn on WS_EX_COMPOSITED
        return cp;
     

这种技术与 Winform 的双缓冲支持的最大区别在于 Winform 的版本一次只能在一个控件上工作。您仍然会看到每个单独的控件绘制本身。这看起来也像是一种闪烁效果,尤其是在未绘制的控制矩形与窗口背景形成鲜明对比的情况下。

【讨论】:

这个解决方案让滚动变慢了。 此解决方案会导致 ElementHost 中托管的 WPF 控件出现问题。控件将无法正确绘制。 如果您有一个带有拆分器容器的表单,请不要使用 WS_EX_COMPOSITED !【参考方案3】:
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 有更多关于在终端服务器上使用它的信息。

【讨论】:

【参考方案4】:
public void EnableDoubleBuffering()

   this.SetStyle(ControlStyles.DoubleBuffer | 
      ControlStyles.UserPaint | 
      ControlStyles.AllPaintingInWmPaint,
      true);
   this.UpdateStyles();

【讨论】:

【参考方案5】:

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

例如:

class Foo : Panel

    public Foo()  DoubleBuffered = true; 

【讨论】:

【参考方案6】:

nobugz 在他的链接中获得了该方法的功劳,我只是重新发布。将此覆盖添加到表单:

protected override CreateParams CreateParams

    get
    
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x02000000;
        return cp;
    

这对我来说效果最好,在 Windows 7 上,当我调整重控件表单的大小时,会出现大的黑块。控件现在弹跳了!但这样更好。

【讨论】:

【参考方案7】:

扩展方法为控件打开或关闭双缓冲

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 DoubleBuffered):

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

【讨论】:

【参考方案8】:

在尝试双缓冲之前,请查看 SuspendLayout()/ResumeLayout() 是否能解决您的问题。

【讨论】:

Suspend/ResumeLayout 没有解决绘画时闪烁的问题。【参考方案9】:

这让我在第三方控制下很多悲痛了两天,直到我找到它为止。

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

只是想传下去。

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

您也可以将控件继承到您自己的类中,并在其中设置属性。如果您倾向于在所有控件上进行大量相同的设置,则此方法也很好。

【讨论】:

【参考方案12】:

我发现只需在表单上设置 DoubleBuffered 设置就会自动设置此处列出的所有属性。

【讨论】:

【参考方案13】:

FWIW

以我之前的工作为基础:Dummy's Solution、Ian Boyd's Solution、Amo's Solution

这是一个使用反射在 PowerShell 中通过SetStyle 设置双缓冲的版本

function Set-DoubleBuffered
<#
.SYNOPSIS
Turns on double buffering for a [System.Windows.Forms.Control] object
.DESCRIPTION
Uses the Non-Public method 'SetStyle' on the control to set the three
style flags recomend for double buffering: 
   UserPaint
   AllPaintingInWmPaint
   DoubleBuffer
.INPUTS
[System.Windows.Forms.Control]
.OUTPUTS
None
.COMPONENT  
System.Windows.Forms.Control
.FUNCTIONALITY
Set Flag, DoubleBuffering, Graphics
.ROLE
WinForms Developer
.NOTES
Throws an exception when trying to double buffer a control on a terminal 
server session becuase doing so will cause lots of data to be sent across 
the line
.EXAMPLE
#A simple WinForm that uses double buffering to reduce flicker
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()

$Pen = [System.Drawing.Pen]::new([System.Drawing.Color]::FromArgb(0xff000000),3)

$Form = New-Object System.Windows.Forms.Form
Set-DoubleBuffered $Form
$Form.Add_Paint(
   param(
      [object]$sender,
      [System.Windows.Forms.PaintEventArgs]$e
   )
   [System.Windows.Forms.Form]$f = $sender
   $g = $e.Graphics
   $g.SmoothingMode = 'AntiAlias'
   $g.DrawLine($Pen,0,0,$f.Width/2,$f.Height/2)
)
$Form.ShowDialog()

.LINK
https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.control.setstyle?view=net-5.0
.LINK
https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.controlstyles?view=net-5.0
#>
   param(
      [parameter(mandatory=$true,ValueFromPipeline=$true)]
      [ValidateScript($_ -is [System.Windows.Forms.Control])]
      #The WinForms control to set to double buffered
      $Control,
      
      [switch]
      #Override double buffering on a terminal server session(not recomended)
      $Force
   )
   begintry
      if([System.Windows.Forms.SystemInformation]::TerminalServerSession -and !$Force)
         throw 'Double buffering not set on terminal server session.'
      
      
      $SetStyle = ([System.Windows.Forms.Control]).GetMethod('SetStyle',
         [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance
      )
      $UpdateStyles = ([System.Windows.Forms.Control]).GetMethod('UpdateStyles',
         [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance
      )
   catch $PSCmdlet.ThrowTerminatingError($PSItem)
   processtry
      $SetStyle.Invoke($Control,@(
         ([System.Windows.Forms.ControlStyles]::UserPaint -bor
           [System.Windows.Forms.ControlStyles]::AllPaintingInWmPaint -bor
           [System.Windows.Forms.ControlStyles]::DoubleBuffer
         ),
         $true
      ))
      $UpdateStyles.Invoke($Control,@())
   catch $PSCmdlet.ThrowTerminatingError($PSItem)

【讨论】:

以上是关于如何在表单上加倍缓冲 .NET 控件?的主要内容,如果未能解决你的问题,请参考以下文章

创建“非可视”.NET 用户控件

如何使用 vb.net 从 MS Access DB 表单中找出 Activex 控件

当用户控件具有焦点时,如何触发表单级事件?

如何将非托管 C++ 表单嵌入到 .NET 应用程序中?

如何禁用 .NET WebBrowser 控件中的缓存?

.Net语言 APP开发平台——Smobiler学习日志:如何在手机上实现表单设计