21

我在 C++/MFC 应用程序中使用 GDI+,我似乎无法在调整窗口大小时避免闪烁。

我已经尝试了这些步骤:

  • 返回 TRUE OnEraseBkGnd();
  • 返回 NULL OnCtlColor()
  • 根据此代码使用双缓冲:

void vwView::OnDraw(CDC* pDC) 
{
   CRect rcClient;
   GetClientRect(rcClient);

   Bitmap bmp(rcClient.Width(), rcClient.Height());
   Graphics graphics(&bmp);

   graphics.DrawImage(m_image, rcClient.left, rcClient.top);

   Graphics grph(pDC->m_hDC);
   grph.DrawImage(&bmp, 0, 0);
}

难道我做错了什么?还是有另一种方法可以实现这一目标?

4

6 回答 6

43

为了完全避免闪烁,您需要在屏幕更新之间完成所有绘图。对于普通的窗口绘制,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_VREDRAWCS_HREDRAW:这将有所帮助,但前提是您不需要大小更改时重绘整个窗口。

通过在WM_PAINT方法之前防止绘制来避免过度绘制的注意事项

当一个窗口的全部或部分失效时,它将被擦除并重新绘制。如前所述,如果您打算重新绘制整个无效区域,则可以跳过擦除。但是,如果您正在使用子窗口,则必须确保父窗口不会同时擦除您的屏幕区域。应该在所有父窗口上设置WS_CLIPCHILDREN样式 -这将防止子窗口(包括您的视图)占用的区域被绘制。

通过在WM_PAINT方法之后防止绘图来避免过度绘制的注意事项

如果您的表单上托管了任何子控件,您将希望使用WS_CLIPCHILDREN样式来避免在它们之上绘制(并随后被它们过度绘制。请注意,这将在一定程度上影响 BitBlt 例程的速度。

高效双缓冲注意事项

现在,每次视图绘制自己时,您都会创建一个新的后台缓冲区图像。对于较大的窗口,这可能表示正在分配和释放大量内存,并导致严重的性能问题。我建议在视图对象中保留一个动态分配的位图,根据需要重新分配它以匹配视图的大小。

请注意,在调整窗口大小时,这将导致与当前系统一样多的分配,因为每个新大小都需要分配一个新的后台缓冲区位图来匹配它 - 您可以通过向上舍入尺寸来减轻痛苦到 4、8、16 等的下一个最大倍数,让您避免对每个微小的大小变化进行重新分配。

请注意,如果自上次渲染到后台缓冲区后窗口的大小没有更改,则在窗口无效时不需要重新渲染它 - 只需将已经渲染的图像 Blt out 到屏幕。

此外,分配与屏幕的位深度匹配的位图。Bitmap您当前使用的构造函数将默认为 32bpp,ARGB 布局;如果这与屏幕不匹配,则必须对其进行转换。考虑使用 GDI 方法CreateCompatibleBitmap()来获取匹配的位图。

最后......我假设您的示例代码就是这样,一个说明性的片段。但是,如果您实际上除了将现有图像渲染到屏幕上之外什么都不做,那么您根本不需要维护后台缓冲区 - 只需直接从图像中 Blt(并提前将图像的格式转换为匹配屏幕)。

于 2008-10-14T00:13:16.693 回答
4

您可以尝试使用老式 GDI 而不是 GDI+ 来写入 DC,尤其是因为您已经在缓冲图像。使用 Bitmap::LockBits 访问原始位图数据,创建 BITMAPINFO 结构,并使用 SetDIBitsToDevice 显示位图。

于 2008-10-13T15:53:24.423 回答
3

确保窗口的窗口类在其样式中不包含 CS_VREDRAW 和 CS_HREDRAW 标志。

请参阅http://msdn.microsoft.com/en-us/library/ms633574(VS.85).aspx

于 2008-10-13T16:30:20.713 回答
3

您可能会通过使用Direct3D来“告诉”您何时发生 vsync 等,从而获得一些牵引力,这样您就可以适时进行 BitBlt/更新。请参阅GDI vsync 以避免撕裂(尽管在某些情况下将事情归结为一个小的 BitBlt 可能“足够好”)。

另请注意,GDI BitBlt 似乎与屏幕垂直同步不同步。请参阅 比 BitBlt 更快

另请注意,如果使用 CAPTUREBLT(允许您捕获透明窗口),则会导致鼠标闪烁(如果未使用 aero)。

于 2012-05-09T14:38:40.300 回答
2

此链接包含一些有用的信息: http: //www.catch22.net/tuts/flicker-free-drawing

(I know this is a very late addition to the thread, but this is for anyone (like me) who found it when looking to reduce flicker in my Win32 app...)

于 2013-08-30T09:55:20.257 回答
0

表单上有子窗口吗?窗口管理器首先通过发送 WM_ERASEBKGND 消息让父窗口擦除其背景,然后发送 wM_PAINT 消息 - 大概这映射到您的 wx::OnDraw 方法。然后它遍历每个子控件并让它们自己绘制。

如果这是您的情况...使用 Vistas 新的 aero 外观将解决您的问题,因为 aero 桌面窗口管理器会自动进行窗口合成。对于较旧的窗口管理器,它是一个皮塔饼。

于 2008-10-13T15:58:23.507 回答