在编程当中,或多或少会接触到图像编程,对于图像编程来说窗口闪烁是个常见的问题,当窗口有大量的复杂的图元数据需要重绘,或者拥有自定义控件中的窗口闪烁问题更是显而易见的。出现闪烁的原因有很多种,大部分原因主要是因为触发WM_PAINT消息时窗体进行了重绘操作,此过程先是用窗体的背景色擦除窗口表面,再把窗体的图像绘制上去,但是如果这两个操作不在同一时间段完成的话,就会先看到背景色(大部分为白色)接着才看到图像,这样就会出现我们所说的窗体闪烁现象。那么如何解决这个问题呢,解决方法有很多,其中有个比较好的方法(个人认为)就是采用双缓冲机制来绘图,基本上可以解决大部分的问题。
双缓冲的原理:尽量快的输出图像,使输出在一个刷新周期内完成,如果输出内容很多比较慢,那么采用内存缓冲的方法,先把要输出的内容在内存准备好,然后一次性输出到窗体上,简单的说来就是在窗口刷新一次的过程中,让所有图元同时显示到窗口中。
在C#中,.Net Framework为编程人员提供了很好的操作双缓冲的方法,为采用双缓冲机制绘制比较复杂的图像数据带来便捷。
下面简单的介绍在C#中实现双缓冲的几种方法。
(1)在应用程序中使用双缓冲的最简便的方法是使用 .NET Framework 为窗体和控件提供的默认双缓冲。通过将 DoubleBuffered 属性设置为 true。
this.DoubleBuffered=true;
(2)使用 SetStyle 方法可以为 Windows 窗体和所创作的 Windows 控件启用默认双缓冲,在窗体或者控件的构造函数中添加如下代码即可:
SetStyle(ControlStyles.ResizeRedraw,true);
SetStyle(ControlStyles.OptimizedDoubleBuffer,true);
SetStyle(ControlStyles.AllPaintingInWmPaint,true);
或
this.SetStyle(ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
this.UpdateStyles();
注:
.net1.1 和 .net 2.0 在处理控件双缓冲上是有区别的。
.net 1.1 中,使用:this.SetStyle(ControlStyles.DoubleBuffer, true);
.net 2.0中,使用:this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
在 C# 中手动管理缓冲图像有两种方法,一种是利用单独开辟内存实现双缓冲这种传统的方法,还有一种是利用 .Net Framework 中独有的BufferedGraphicsContext类实现。
方法一: 自己开辟一个缓冲区(如一个不显示的Bitmap对象),在其中绘制完成后,再一次性显示,代码如下:
//1、在内存中建立一块“虚拟画布”
Bitmap bmp = new Bitmap(200,200);
//2、获取这块内存画布的Graphics引用
Graphics bufferGraphics = Graphics.FromImage(bmp);
//3、在这块内存画布上绘图
bufferGraphics.Clear(this.BackColor);
bufferGraphics.DrawRectangle(Pens.Black,0,0,bmp.Width -1,bmp.Height -1);
bufferGraphics.DrawEllipse(Pens.Red,10,10,100,50);
bufferGraphics.DrawLine(Pens.Green,10,100,100,200);
//4、将内存画布画到窗口中
using(Graphics g = e.Graphics)
{
g.DrawImage(bmp, 10, 10);
}
//5. 释放资源
bmp.Dispose();
bufferGraphics.Dispose();
方法二:
对于更高级的双缓存情形,可以使用 .NET Framework 类实现自己的双缓存逻辑。负责单独分配和管理图形缓冲区的类是BufferedGraphicsContext 类。每个应用程序都有自己的默认 BufferedGraphicsContext 来管理此应用程序的所有默认双缓冲。提供调用Current 可以检索对此实例的引用。通过调用 Allocate 方法可以创建与屏幕上的绘图图面关联的 BufferedGraphics 类的实例。此方法创建一个与特定呈现图面(如窗体或控件)关联的 BufferedGraphics 实例。创建 BufferedGraphics 实例后,可以将图形绘制到由该实例的Graphics 属性表示的缓冲区。执行所有图形操作后,可通过调用 Render 方法将缓冲区的内容复制到屏幕上。以下代码把方法一实现的效果用此方法来实现:
BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics,e.ClipRectangle);
Graphics g = myBuffer.Graphics;
g.Clear(this.BackColor);
g.DrawRectangle(Pens.Black, 10, 10, 200, 200);
g.DrawEllipse(Pens.Red, 10, 10, 100, 50);
g.DrawLine(Pens.Green, 10, 100, 100, 200);
myBuffer.Render(e.Graphics); //呈现图像至关联的Graphics
myBuffer.Dispose();
g.Dispose();
至此,双缓冲问题解决,两种方式的实现效果都一样,私以为第二种方法占有的内存很少,不会出现内存泄露!
成员名称 | 说明 |
---|---|
ContainerControl | 如果为 true,则控件是类似容器的控件。 |
UserPaint | 如果为 true,控件将自行绘制,而不是通过操作系统来绘制。 如果为 false,将不会引发 Paint 事件。 此样式仅适用于派生自 Control 的类。 |
Opaque | 如果为 true,则控件被绘制为不透明的,不绘制背景。 |
ResizeRedraw | 如果为 true,则在调整控件大小时重绘控件。 |
FixedWidth | 如果为 true,则自动缩放时,控件具有固定宽度。 例如,如果布局操作尝试重新缩放控件以适应新的Font,则控件的 Width 将保持不变。 |
FixedHeight | 如果为 true,则自动缩放时,控件具有固定高度。 例如,如果布局操作尝试重新缩放控件以适应新的Font,则控件的 Height 将保持不变。 |
StandardClick | 如果为 true,则控件将实现标准 Click 行为。 |
Selectable | 如果为 true,则控件可以接收焦点。 |
UserMouse | 如果为 true,则控件完成自己的鼠标处理,因而鼠标事件不由操作系统处理。 |
SupportsTransparentBackColor | 如果为 true,控件接受 alpha 组件小于 255 的 BackColor 以模拟透明。 仅在 UserPaint 位设置为 true并且父控件派生自 Control 时才模拟透明。 |
StandardDoubleClick | 如果为 true,则控件将实现标准 DoubleClick 行为。 如果 StandardClick 位未设置为 true,则忽略此样式。 |
AllPaintingInWmPaint | 如果为 true,控件将忽略 WM_ERASEBKGND 窗口消息以减少闪烁。 仅当 UserPaint 位设置为 true 时,才应当应用该样式。 |
CacheText | 如果为 true,控件保留文本的副本,而不是在每次需要时从 Handle 获取文本副本。 此样式默认为false。 此行为提高了性能,但使保持文本同步变得困难。 |
EnableNotifyMessage | 如果为 true,则为发送到控件的 WndProc 的每条消息调用 OnNotifyMessage 方法。 此样式默认为false。 EnableNotifyMessage 在部分可信的情况下不工作。 |
DoubleBuffer | 如果为 true,则绘制在缓冲区中进行,完成后将结果输出到屏幕上。 双重缓冲区可防止由控件重绘引起的闪烁。 如果将 DoubleBuffer 设置为 true,则还应当将 UserPaint 和 AllPaintingInWmPaint 设置为true。 |
OptimizedDoubleBuffer | 如果为 true,则该控件首先在缓冲区中绘制,而不是直接绘制到屏幕上,这样可以减少闪烁。 如果将此属性设置为 true,则还应当将 AllPaintingInWmPaint 设置为 true。 |
UseTextForAccessibility | 指定该控件的 Text 属性的值,如果已设置,则可确定该控件的默认 Active Accessibility 名称和快捷键。 |