移动/调整窗口大小时闪烁

时间:2014-11-02 14:11:37

标签: windows image winapi flicker

我开发了一个显示jpeg图像的应用程序。它可以显示4个图像,每个图像在屏幕的每个象限中。它使用了4个窗口。窗口没有边框(框架)和标题栏。 加载新图像时,会根据新图像调整窗口大小,然后显示图像。

特别是当窗口变大时,通常会出现闪烁。在我看到裂缝时,似乎在显示新内容之前调整大小时会移动旧内容。

我咨询了许多资源并使用了所有技巧:

  • 该窗口仅包含样式CS_DBLCLKS(无CS_HREDRAWCS_VREDRAW);

  • 背景画笔为NULL;

  • WM_ERASEBKGND返回1;

  • WM_NCPAINT返回0;

  • WM_NCCALCSIZE告诉我对齐未移动的一侧(你能告诉它丢弃客户区吗?);

  • WM_WINDOWPOSCHANGING返回0;

  • SetWindowPos有标记SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING

然而,在调整窗口大小时会出现闪烁(或内容移动)。我想要的是:

  • SetWindowPos为新的大小和位置;

  • InvalidateRect(hWnd,NULL,FALSE);

  • UpdateWindow(HWND);

没有任何绘画,背景删除或内容移动到WM_PAINT

WM_PAINT

hDC= BeginPaint (hWnd, &ps);
hMemDC= CreateCompatibleDC (hDC);
hOldBitmap = SelectObject (hMemDC, hNewBitmap);
BitBlt (hDC,...,hMemDC,0,0,SRCCOPY);
SelectObject (hMemDC, hOldBitmap);
DeleteDC (hMemDC);
EndPaint (hWnd, &ps);

有人能告诉我是否/我在哪里犯了一个错误,导致窗口的旧内容被移动?

硬件等:HP Elitebook Core7 64位上的Windows 7,NVIDIA Quadro K1000m驱动程序9.18.13.3265(更新至341.44)。

<小时/> 更新(7月&#39; 17)

我已经在另一台Windows计算机上看到了该程序的行为(Windows 8/10)。它似乎不是NVIDIA的显示驱动程序。

当将平铺到屏幕中心的窗口(右下角= w / 2,h / 2)调整为左上角或左上角(0,0)时,行为最明显。

我可能在计算WM_NCCALCSIZE消息时遇到问题,告诉Windows不要做任何事情。有人可以为我的目的举例计算吗?另请参阅How do I force windows NOT to redraw anything in my dialog when the user is resizing my dialog?

4 个答案:

答案 0 :(得分:0)

你有一个令人印象深刻的反闪烁技巧列表: - )

我不知道这是否重要(因为它取决于你的工具窗口是如何创建的,特别是如果它们是普通父母的子窗口): 尝试在工具窗口的父窗口中设置窗口样式WS_CLIPCHILDREN(如果有的话)。

如果未设置,父窗口将擦除它的(整个)背景,然后将绘制消息转发到子窗口,这将导致闪烁。如果设置了WS_CLIPCHILDREN,则父窗口不会对子窗口占用的客户区域执行任何操作。因此,儿童窗户的区域不会被抽出两次,也没有闪烁的机会。

答案 1 :(得分:0)

这是一个理论而不是答案:

默认情况下,在现代Windows中,您的窗口只是视频卡上的纹理,桌面窗口管理器将其映射到屏幕上的矩形。您似乎已经做了一切必要的工作,以确保纹理一下子得到更新。

但是当您调整窗口大小时,桌面合成器可能会立即更新其几何体,导致(仍未更改的)纹理显示在屏幕上的新位置。只有在你做油漆时才会更新纹理。

您可以通过暂时关闭桌面合成来测试此理论。在Windows 7上,导航到“系统属性”,选择“高级”选项卡,在“性能选择设置...”下,在“视觉效果”选项卡上,取消选择“#34;启用桌面组合&#34;设置。然后尝试重现问题。如果它消失了,那么它支持(但并不能完全证明)我的理论。

(请记住重新启用合成,因为大多数时候您的大部分用户都会如此运行。)

如果理论是正确的,那么目标似乎是在窗口调整大小后尽快进入绘画。如果时间窗口足够小,则两者都可能在监视器刷新周期内发生,并且不会出现闪烁。

具有讽刺意味的是,你在这里消除闪烁的努力可能会对你不利,因为你有意压制了通常由SetWindowPos引起的失效和重绘,直到你在以后的步骤中手动完成。

调试提示:尝试在流程中的关键点引入延迟(例如Sleep(1000);),以便您可以看到调整大小和重绘是否实际在屏幕上呈现为两个不同的步骤。

答案 2 :(得分:0)

除了你的技巧列表之外。在使用左/上边缘调整窗口大小时,我的戴尔XPS笔记本上的闪烁也存在同样的问题。我已经尝试过你提到的所有技巧。据我所知,窗口边框是在GPU中绘制的,窗口内容是在GDI子系统中准备的,并传输到视频内存中进行窗口合成(Windows 8.1中引入的DWM)。我试图完全删除GDI渲染(设置WS_EX_NOREDIRECTIONBITMAP样式),这使得窗口没有任何内容,然后使用DXGI子系统直接创建内容表面(使用CreateSwapChainForComposition,有关如何执行此操作的示例)。但问题仍然存在。渲染窗口框架和调整大小/显示内容表面之间仍然存在滞后。

然而,它可以解决您的问题,因为您没有边框,您不会注意到这种滞后。但是你可以完全控制窗口重绘,它将在GPU端进行。

答案 3 :(得分:0)

您确实有很多技巧。

首先,我可以建议一些现有技巧的变种,这些变种可能特别对XP / Vista / 7有所帮助,其次,我想提到您在Win8 / 10上看到的持续闪烁可能来自何处,以及一些减少技巧。那个。

首先,尽管在其他OP帖子中提出了相反的建议,但您可能会发现,如果设置了以前避免的CS_HREDRAW|CS_VREDRAW窗口样式,则实际上消除了内部内部完成的BitBlt SetWindowPos Windows在调整窗口大小时会这样做(但是-这是令人困惑的部分-在Windows 8/10上,您仍然会从其他来源看到闪烁...更多信息,请参见下文。)

如果您不想包含CS_HREDRAW|CS_VREDRAW,也可以拦截WM_WINDOWPOSCHANGING(首先将其传递到DefWindowProc上)并设置WINDOWPOS.flags |= SWP_NOCOPYBITS,以禁用{{ 1}}在Windows调整窗口大小期间对BitBlt的内部调用中。

或者,您可以添加SetWindowPos()技巧,让它返回一组值,这些值将告诉Windows在其顶部仅WM_NCCALCSIZE 1个像素,即使{{1} }确实发生了,但它显然没有做任何事情:

BitBlt

这一切都很好,但是为什么Windows 8/10看起来这么糟糕?

Windows 8/10 Aero下的应用程序不会直接绘制到屏幕上,而是绘制到屏幕外缓冲区,然后由邪恶的DWM.exe窗口管理器合成。实际上,DWM在现有的旧版XP / Vista / 7 BitBlt行为之上又添加了case WM_NCCALCSIZE: { RECT ocr; // old client rect if (wParam) { NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam; // np->rgrc[0] is new window rect // np->rgrc[1] is old window rect // np->rgrc[2] is old client rect ocr = np->rgrc[2]; } else { RECT *r = (RECT *)lParam; // *r is window rect } // first give Windoze a crack at it lRet = DefWindowProc(hWnd, uMsg, wParam, lParam); if (wParam) { NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam; // np->rgrc[0] is new client rect computed // np->rgrc[1] is going to be dst blit rect // np->rgrc[2] is going to be src blit rect // ncr = np->rgrc[0]; RECT &dst = np->rgrc[1]; RECT &src = np->rgrc[2]; // FYI DefWindowProc gives us new client rect that // - in y // - shares bottom edge if user dragging top border // - shares top edge if user dragging bottom border // - in x // - shares left edge if user dragging right border // - shares right edge if user dragging left border // src = ocr; dst = ncr; // - so src and dst may have different size // - ms dox are totally unclear about what this means // https://docs.microsoft.com/en-us/windows/desktop/ // winmsg/wm-nccalcsize // https://docs.microsoft.com/en-us/windows/desktop/ // api/winuser/ns-winuser-tagnccalcsize_params // - they just say "src is clipped by dst" // - they don't say how src and dst align for blt // - resolve ambiguity // essentially disable BitBlt by making src/dst same // make it 1 px to avoid waste in case Windoze still does it dst.right = dst.left + 1; dst.bottom = dst.top + 1; src.right = dst.left + 1; src.bottom = dst.top + 1; lRet = WVR_VALIDRECTS; } else // wParam == 0: Windoze wants us to map a single rect w->c { RECT *r = (RECT *)lParam; // *r is client rect } return lRet; 型行为的另一层。

DWM的blit行为更加疯狂,因为它们不仅复制客户区,而且实际上在旧客户区的边缘复制像素以制作新客户区。不幸的是,使DWM不做主宰比仅传递一些额外的标志要困难得多。

我没有100%的解决方案,但希望以上信息对您有所帮助,请查看此常见问题解答,以获取可帮助您减少应用中DWM层冲突的可能性的计时技巧:< / p>

How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?