不要闪烁!使用双缓冲!






4.83/5 (53投票s)
演示了几种克服恼人闪烁问题的方法
引言
闪烁是一个常见的问题,每个在 Windows 窗体环境中编程的人都知道。我们都知道,即使 Windows 任务管理器在选择进程列表中的进程时也会闪烁。
如果你曾经研究过这个问题,你可能已经注意到最常见的解决方案是双缓冲。
解释
双缓冲是一种技术,我们将所有的图形需求绘制到存储在内存(缓冲区)中的图像中,并且在我们完成所有的绘图需求之后,我们将完整的图像从内存绘制到屏幕上。 这将屏幕上的绘图(严重影响应用程序性能的操作)集中到单个操作,而不是许多小的操作。
一个容易理解的例子是使用具有多个层的 ProgressBar
- 背景层
- 边框层
- 进度层
- 百分比层
对于这些层中的每一层,我们需要调用一些绘图操作,并且在每次绘图操作之后,控件会将其自身重绘到屏幕上。 现在,如果刷新率较低,我们将没有任何问题,但是如果我们加快刷新率,则会发生闪烁(眨眼)。
我们通过将所有层绘制到位于内存中的图像中来解决此问题,并且在将所有层绘制到此图像中之后,我们将图像绘制到屏幕上。 这极大地提高了性能。
技术
注意:以下提到的所有技术都用于本文提供的示例源代码中,除了第一个来自 .NET Framework 1.1 的技术,并且源代码是针对 .NET Framework 2.0。
开始之前您应该知道的事情
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
绘制控件时,会调用两个函数,
OnPaint
和OnPaintBackground
。 设置此标志后,它将忽略OnPaintBackground
函数,并且OnPaint
函数负责绘制背景和前景。SetStyle(ControlStyles.UserPaint, true);
当此标志设置为
true
时,控件会自行绘制,而不是由系统操作绘制。- 提示(由 Tim McCurdy 提供):
SetStyle(ControlStyles.ResizeRedraw, true);
设置此标志会导致控件在调整大小时重绘自身。
ProgressBar
绘图对于所有这些示例,我调用一个名为
DrawProgressBar
的函数。 传递给它的参数是一个Graphics
实例,用于绘图private void DrawProgressBar(Graphics ControlGraphics) { // draw background ControlGraphics.FillRectangle(Brushes.Black, ClientRectangle); // draw border ControlGraphics.DrawRectangle(Pens.White, ClientRectangle); // draw progress ControlGraphics.FillRectangle(Brushes.SkyBlue, 0, 0, this.Width * ProgressBarPercentValue, this.Height); // draw percent ControlGraphics.DrawString(ProgressBarPercentValue.ToString(), this.Font, Brushes.Red, new Point(this.Width / 2, this.Height / 2)); }
开始
-
.NET Framework 1.1 内置双缓冲
public partial class DoubleBufferedControl : Control { public DoubleBufferedControl() { InitializeComponent(); this.SetStyle( ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true); } protected override void OnPaint(PaintEventArgs pe) { // we draw the progressbar normally // with the flags sets to our settings DrawProgressBar(pe.Graphics); } }
这项技术来自 .NET Framework 1.1,并提供了一些双缓冲支持。 从我测试的结果来看,这项技术不是很好,我更喜欢对 .NET Framework 1.1 使用手动技术(稍后将介绍)。
-
.NET Framework 2.0 内置双缓冲
public class DoubleBufferedControl : Control { public DoubleBufferedControl() { InitializeComponent(); this.SetStyle( ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); } protected override void OnPaint(PaintEventArgs pe) { // we draw the progressbar normally with // the flags sets to our settings DrawProgressBar(pe.Graphics); } }
好吧,在 .NET Framework 2.0 中,双缓冲技术的易用性有了很大的改进。 通过使用这项技术获得的性能非常好,我建议所有不想进行太多编码的人使用它。
我应该提到,当我们设置
Control.DoubleBuffered
为true
时,它会将ControlStyles.AllPaintingInWmPaint
和ControlStyles.OptimizedDoubleBuffer
设置为true
。 -
.NET Framework 1.1 的手动解决方案
我们在这里所做的是自己创建双缓冲,并通过覆盖控件的
OnPaint
事件或您可能想要在任何其他地方使用的事件来实现它public partial class DoubleBufferedControl : Control { const Bitmap NO_BACK_BUFFER = null; const Graphics NO_BUFFER_GRAPHICS = null; Bitmap BackBuffer; Graphics BufferGraphics; public DoubleBufferedControl() { InitializeComponent(); Application.ApplicationExit += new EventHandler(MemoryCleanup); SetStyle(ControlStyles.AllPaintingInWmPaint, true); BackBuffer = new Bitmap(this.Width, this.Height); BufferGraphics = Graphics.FromImage(BackBuffer); } private void MemoryCleanup(object sender, EventArgs e) { // clean up the memory if (BackBuffer != NO_BACK_BUFFER) BackBuffer.Dispose(); if (BufferGraphics != NO_BUFFER_GRAPHICS) BufferGraphics.Dispose(); } protected override void OnPaint(PaintEventArgs pe) { // we draw the progressbar into the image in // the memory DrawProgressBar(BufferGraphics); // now we draw the image into the screen pe.Graphics.DrawImageUnscaled(BackBuffer); } private void DoubleBufferedControl_Resize(object sender, EventArgs e) { if (BackBuffer != NO_BACK_BUFFER) BackBuffer.Dispose(); BackBuffer = new Bitmap(this.Width, this.Height); BufferGraphics = Graphics.FromImage(BackBuffer); this.Refresh(); } }
-
.NET Framework 2.0 的手动解决方案
在 .NET Framework 2.0 中,我们仍然可以使用手动方式。 Microsoft 为我们提供了一些有用的工具,使其更加容易。 新工具是
BufferedGraphicsContext
和BufferedGraphics
。BufferedGraphicsContext
为我们提供了一个替代缓冲区,而不是我们在 .NET Framework 1.1 中使用的Bitmap
,而BufferedGraphics
处理所有图形操作,例如使用Render()
函数将缓冲图像绘制到屏幕上,等等。public class DoubleBufferedControl : Control { const BufferedGraphics NO_MANAGED_BACK_BUFFER = null; BufferedGraphicsContext GraphicManager; BufferedGraphics ManagedBackBuffer; public DoubleBufferedControl() { InitializeComponent(); Application.ApplicationExit += new EventHandler(MemoryCleanup); SetStyle(ControlStyles.AllPaintingInWmPaint, true); GraphicManager = BufferedGraphicsManager.Current; GraphicManager.MaximumBuffer = new Size(this.Width + 1, this.Height + 1); ManagedBackBuffer = GraphicManager.Allocate(this.CreateGraphics(), ClientRectangle); } private void MemoryCleanup(object sender, EventArgs e) { // clean up the memory if (ManagedBackBuffer != NO_MANAGED_BACK_BUFFER) ManagedBackBuffer.Dispose(); } protected override void OnPaint(PaintEventArgs pe) { // we draw the progressbar into the image in the memory DrawProgressBar(ManagedBackBuffer.Graphics); // now we draw the image into the screen ManagedBackBuffer.Render(pe.Graphics); } private void DoubleBufferedControl_Resize(object sender, EventArgs e) { if (ManagedBackBuffer != NO_MANAGED_BACK_BUFFER) BackBufferManagedBackBufferDispose(); GraphicManager.MaximumBuffer = new Size(this.Width + 1, this.Height + 1); ManagedBackBuffer = GraphicManager.Allocate(this.CreateGraphics(), ClientRectangle); this.Refresh(); } }
结论
双缓冲是一种很好且易于使用的技术,我认为任何曾经处理过一些图形编程的人都应该知道。 我也很高兴看到 Microsoft 花费了大量时间来提高 .NET Framework 的 GUI 性能,并为我们提供了一些更好的工具来处理它们,而不是浪费时间编写一些即兴代码。
历史
- 2006 年 1 月 29 日:初始版本