程序清单3-16 利用双缓冲在屏幕上绘制弹跳的颜色块
/// <summary>
/// Draw all of the graphics for our scene
/// </summary>
private void DrawScene(Graphics gfx)
{
// Have we initialised our back buffer yet?
if (backBuffer == null)
{
// We haven't, so initialise it now.
backBuffer = new Bitmap(this.Width, this.Height);
}
// Create a graphics object for the backbuffer
using (Graphics buffergfx = Graphics.FromImage(backBuffer))
{
// Clear the back buffer
buffergfx.Clear(Color.White);
// Draw our box into the back buffer at its current location
using (Brush b = new SolidBrush(Color.Blue))
{
buffergfx.FillRectangle(b, xpos, ypos, boxSize, boxSize);
}
}
// Finally, copy the content of the entire backbuffer to the window
gfx.DrawImage(backBuffer, 0, 0);
}
private void Method2_DoubleBuffering_Paint(object sender, PaintEventArgs e)
{
// Call DrawScene to do the drawing for us
DrawScene(e.Graphics);
}
再次运行这个示例,这次选择DoubleBuffer生成方式。在运行结果中请注意下面两点:
● 颜色块的移动速度比上一个示例要慢;
● 动画仍然很闪烁——实际上,甚至比前面的示例还要严重。
颜色块的移动速度比使用第一种方法时慢,因为要做更多的工作:不只是简单地绘制颜色块,现在还要清空整个后台缓冲区,并且将后台缓冲区中的内容复制到屏幕上。这些需要一些时间来完成。将这个开销最小化是另一类优化,我们将在第4章中进行讨论。在第5章中,我们还将讨论在不同的运行环境下如何使速度保持一致。
发生闪烁的原因是尽管在后台缓冲区中创建了所有的图形,但在每次重绘时,GDI仍然会自动对窗体进行清空。我们需要合适地处理这种闪烁才可以在任何窗体中都显示平滑的动画。
可以很容易地关闭窗体的自动清除功能。我们可以对Form类的OnPaintBackground方法进行重写,在其中不调用基类中的方法(清除操作实际上就是发生在这里),并且也不进行任何其他操作。这样在OnPaintBackground事件的处理过程中,Paint事件发生之前屏幕上显示的所有内容都会保留下来。
将后台绘制关闭后,现在将后台缓冲区内容复制到窗体中时不会发生任何闪烁。后台缓冲区直接替换了先前显示的图像,不受中间绘图的影响。在示例应用程序中选择SmoothDraw生成方式来查看其运行情况。最终我们实现了颜色块的平滑的、无闪烁的运动。
我们在使用GDI创建游戏时就将采用这种方法。后台缓冲区中包含了整个屏幕的全部图像,在游戏中当任何物体发生移动时,就将后台缓冲区复制到窗体中。