为了完全避免闪烁,您需要在屏幕更新之间完成所有绘图。对于普通的窗口绘制,Windows 没有提供任何简单的方法来完成此操作(Vista 通过DWM提供复合绘制,但即使在运行 Vista 的系统上也不能依赖它)。因此,尽量减少闪烁的最佳方法是尽可能快地绘制所有内容(通过增加在刷新周期内完成所有绘制的机会来减少撕裂),并避免过度绘制(绘制屏幕的一部分,然后再绘制其他内容)顶部:存在向用户展示部分绘制屏幕的风险)。
让我们讨论一下到目前为止这里介绍的技术:
Do-nothing OnEraseBkgnd():通过防止窗口的无效区域被窗口的背景颜色填充,有助于避免过度绘制。无论如何,当您在WM_PAINT处理期间再次绘制整个区域时很有用,例如在双缓冲绘图的情况下......但请参阅有关通过在WM_PAINT方法之后防止绘图来避免过度绘制的注意事项。
返回 NULL 为OnCtlColor():这实际上不应该做任何事情......除非您的表单上有子控件。在这种情况下,请参阅通过在WM_PAINT方法之后防止绘图来避免过度绘制的注意事项。
双缓冲绘图:通过将实际的屏幕绘图减少到单个BitBLT来帮助避免撕裂(以及潜在的过度绘制)。但是可能会影响绘图所需的时间:不能使用硬件加速(尽管使用 GDI+,使用任何硬件辅助绘图的机会都很渺茫),必须为每次重绘创建和填充屏幕外位图,并且每次重绘都必须重绘整个窗口。请参阅关于高效双缓冲的说明。
对 BitBlt 使用 GDI 调用而不是 GDI+:这通常是个好主意 -Graphics::DrawImage()
可能会非常慢。我什至发现BitBlt()
在某些系统上正常的 GDI 调用速度更快。玩这个,但只有在先尝试了一些其他建议之后。
避免在每次调整大小时强制完全重绘的窗口类样式(CS_VREDRAW,CS_HREDRAW):这将有所帮助,但前提是您不需要在大小更改时重绘整个窗口。
通过在WM_PAINT方法之前防止绘制来避免过度绘制的注意事项
当一个窗口的全部或部分失效时,它将被擦除并重新绘制。如前所述,如果您打算重新绘制整个无效区域,则可以跳过擦除。但是,如果您正在使用子窗口,则必须确保父窗口不会同时擦除您的屏幕区域。应该在所有父窗口上设置WS_CLIPCHILDREN样式 -这将防止子窗口(包括您的视图)占用的区域被绘制。
通过在WM_PAINT方法之后防止绘图来避免过度绘制的注意事项
如果您的表单上托管了任何子控件,您将希望使用WS_CLIPCHILDREN样式来避免在它们之上绘制(并随后被它们过度绘制。请注意,这将在一定程度上影响 BitBlt 例程的速度。
高效双缓冲注意事项
现在,每次视图绘制自己时,您都会创建一个新的后台缓冲区图像。对于较大的窗口,这可能表示正在分配和释放大量内存,并会导致严重的性能问题。我建议在视图对象中保留一个动态分配的位图,根据需要重新分配它以匹配视图的大小。
请注意,在调整窗口大小时,这将导致与当前系统一样多的分配,因为每个新大小都需要分配一个新的后台缓冲区位图来匹配它 - 您可以通过向上舍入尺寸来减轻痛苦到 4、8、16 等的下一个最大倍数,让您避免对每个微小的大小变化进行重新分配。
请注意,如果自上次渲染到后台缓冲区后窗口的大小没有更改,则在窗口无效时不需要重新渲染它 - 只需将已经渲染的图像 Blt out 到屏幕。
此外,分配与屏幕的位深度匹配的位图。Bitmap
您当前使用的构造函数将默认为 32bpp,ARGB 布局;如果这与屏幕不匹配,则必须对其进行转换。考虑使用 GDI 方法CreateCompatibleBitmap()
来获取匹配的位图。
最后......我假设您的示例代码就是这样,一个说明性的片段。但是,如果您实际上除了将现有图像渲染到屏幕上之外什么都不做,那么您根本不需要维护后台缓冲区 - 只需直接从图像中 Blt(并提前将图像的格式转换为匹配屏幕)。