简体   繁体   English

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

[英]Flicker when moving/resizing window

I have developed an application to display jpeg images. 我开发了一个显示jpeg图像的应用程序。 It can display 4 images, one in each quadrant of the screen. 它可以显示4个图像,每个图像在屏幕的每个象限中。 It uses 4 windows for that. 它使用了4个窗口。 The windows have no border (frame) nor titlebar. 窗口没有边框(框架)和标题栏。 When a new image is loaded, the window size is adjusted for the new image and then the image is displayed. 加载新图像时,将为新图像调整窗口大小,然后显示图像。

Especially when the window is made larger, there is often a flicker. 特别是当窗口变大时,通常会出现闪烁。 With my eyes to slits, it seems that the old contents is moved when resizing before the new contents is displayed. 在我看到切口时,似乎在显示新内容之前调整大小时会移动旧内容。

I consulted many resources and used all tricks: 我咨询了很多资源并使用了所有技巧:

  • the window has only style CS_DBLCLKS (no CS_HREDRAW or CS_VREDRAW ); 窗口只有样式CS_DBLCLKS (没有CS_HREDRAWCS_VREDRAW );

  • the background brush is NULL; 背景画笔为NULL;

  • WM_ERASEBKGND returns 1; WM_ERASEBKGND返回1;

  • WM_NCPAINT returns 0; WM_NCPAINT返回0;

  • WM_NCCALCSIZE tells to align to the side not moved (can you tell it to discard the client area?); WM_NCCALCSIZE告诉对齐未移动的一侧(你能告诉它丢弃客户区吗?);

  • WM_WINDOWPOSCHANGING returns 0; WM_WINDOWPOSCHANGING返回0;

  • SetWindowPos has flags SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING SetWindowPos有标志SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING . SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING

Still, the flicker (or the contents move) occurrs when resizing the window. 但是,在调整窗口大小时,会出现闪烁(或内容移动)。 What I want is to: 我想要的是:

  • SetWindowPos to new size and position; SetWindowPos到新的大小和位置;

  • InvalidateRect (hWnd, NULL, FALSE); InvalidateRect(hWnd,NULL,FALSE);

  • UpdateWindow(hWnd); UpdateWindow(HWND);

without any painting, background erasing or content moving until WM_PAINT . 没有任何绘画,背景删除或内容移动,直到WM_PAINT

WM_PAINT does: 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);

Can anyone tell me if/where I make a mistake that causes the old content of the window to be moved? 任何人都可以告诉我是否/我在哪里犯了错误导致窗口的旧内容被移动?

Hardware etc: Windows 7 on HP Elitebook Core7 64 bits with NVIDIA Quadro K1000m driver 9.18.13.3265 (updated to 341.44). 硬件等:HP Elitebook Core7 64位上的Windows 7,NVIDIA Quadro K1000m驱动程序9.18.13.3265(更新至341.44)。


UPDATE (Jul '17) 更新 (17年7月)

I have seen the behavior of the pogram also on another Windows computer (Windows 8/10). 我已经在另一台Windows计算机上看到了该程序的行为(Windows 8/10)。 It does not seem to be the NVIDIA display driver. 它似乎不是NVIDIA的显示驱动程序。

The behavior is the most visible when resizing a window tiled to the centre of the screen (right bottom = w/2, h/2) to the left or left upper corner (0, 0). 调整平铺到屏幕中心(右下角= w / 2,h / 2)到左上角或左上角(0,0)的窗口大小时,行为最明显。

I may have problems with the calculations for the WM_NCCALCSIZE message to tell Windows not to do anything. 我可能在WM_NCCALCSIZE消息的计算上遇到问题,告诉Windows不要做任何事情。 Could anyone give an example calculation for my purpose? 有人可以为我的目的举例计算吗? See also How do I force windows NOT to redraw anything in my dialog when the user is resizing my dialog? 另请参阅当用户调整对话框大小时,如何强制窗口不重绘对话框中的任何内容?

You have an impressive list of anti-flickering tricks :-) 你有一个令人印象深刻的反闪烁技巧列表:-)

I don't know if this is of importance (since it depends on how your tool windows are created and espacially if they are child windows to a common parent): Try setting window style WS_CLIPCHILDREN in the parent window of the tool windows (if there is one). 我不知道这是否重要(因为它取决于你的工具窗口是如何创建的,特别是如果它们是普通父项的子窗口):尝试在工具窗口的父窗口中设置窗口样式WS_CLIPCHILDREN (如果有的话)是一个)。

If not set the parent window will erase it's (entire) background and then forward the paint messages to the child windows which will cause flickering. 如果未设置,则父窗口将擦除它的(整个)背景,然后将绘制消息转发到子窗口,这将导致闪烁。 If WS_CLIPCHILDREN is set the parent window does nothing to the client area occupied by child windows. 如果设置了WS_CLIPCHILDREN则父窗口不会对子窗口占用的客户区域执行任何操作。 As a result the area of child windows isn't drawn twice and there is no chance for flickering. 结果,子窗口的区域没有被绘制两次,并且没有闪烁的机会。

This is a theory more than an answer: 这是一个理论而不是答案:

By default, in modern Windows, your window is just a texture on the video card, and the desktop window manager is mapping that to a rectangle on the screen. 默认情况下,在现代Windows中,您的窗口只是视频卡上的纹理,桌面窗口管理器将其映射到屏幕上的矩形。 You've seem to have done everything necessary to make sure that texture gets updated in one fell swoop. 您似乎已经做了一切必要的工作,以确保纹理一下子得到更新。

But when you resize the window, perhaps the desktop compositor immediately updates its geometry, causing the (still unchanged) texture to be appear in the new position on the screen. 但是当您调整窗口大小时,桌面合成器可能会立即更新其几何体,导致(仍未更改的)纹理显示在屏幕上的新位置。 It's only later, when you do the paint, that the texture is updated. 只有在你做油漆时才会更新纹理。

You can test this theory by temporarily turning off desktop compositing. 您可以通过暂时关闭桌面合成来测试此理论。 On Windows 7, you navigate to System Properties, choose the Advanced tab, under Performance choose Settings..., on the Visual Effects tab, deselect the "Enable desktop composition" setting. 在Windows 7上,导航到“系统属性”,选择“高级”选项卡,在“性能选择设置...”下,在“视觉效果”选项卡上,取消选中“启用桌面组合”设置。 Then try to reproduce the problem. 然后尝试重现问题。 If it goes away, then that supports (but doesn't absolutely prove) my theory. 如果它消失了,那么这支持(但并不是绝对证明)我的理论。

(Remember to re-enable compositing, since that's how most of your users will be running most of the time.) (请记住重新启用合成,因为这是大多数用户大多数时间都在运行的方式。)

If the theory is true, it seems the goal is to get to the paint as soon possible after the window resize. 如果理论是正确的,那么似乎目标是在窗口调整大小后尽快进入绘画。 If the window of time is small enough, then both could happen within a monitor refresh cycle and there would be no flicker. 如果时间窗口足够小,则两者都可能在监视器刷新周期内发生,并且不会出现闪烁。

Ironically, your efforts to eliminate flicker may be working against you here, since you've intentionally suppressed the invalidation and redraw that would normally result from SetWindowPos until you do it manually at a later step. 具有讽刺意味的是,你在这里消除闪烁的努力可能对你不利,因为你有意抑制了通常由SetWindowPos产生的失效和重绘,直到你在后面的步骤中手动完成。

A debugging tip: Try introducing delays (eg, Sleep(1000); ) at key points in the process so you can see whether the resize and redraw are actually rendering on screen as two distinct steps. 调试提示:尝试在过程中的关键点引入延迟(例如, Sleep(1000); ),以便您可以看到调整大小和重绘是否实际在屏幕上呈现为两个不同的步骤。

Just in addition to your list of tricks. 除了你的技巧列表。 I have the same problem with flicker on my Dell XPS notebook while resizing window using left/top edge. 在使用左/上边缘调整窗口大小时,我的戴尔XPS笔记本上的闪烁也存在同样的问题。 I've tried all the tricks you mentioned. 我已经尝试过你提到的所有技巧。 As far as I understand window border is drawn in GPU and the window content is prepared in GDI subsystem and transferred to video memory for window composition (DWM introduced in Windows 8.1). 据我所知,窗口边框是在GPU中绘制的,窗口内容是在GDI子系统中准备的,并传输到视频内存中进行窗口合成(Windows 8.1中引入的DWM)。 I tried to remove GDI rendering completely (setting WS_EX_NOREDIRECTIONBITMAP style) which makes window without any content, and then create content surface directly using DXGI subsystem (using CreateSwapChainForComposition, there are few examples on how to do this). 我试图完全删除GDI渲染(设置WS_EX_NOREDIRECTIONBITMAP样式),这使得窗口没有任何内容,然后使用DXGI子系统直接创建内容表面(使用CreateSwapChainForComposition,有关如何执行此操作的示例)。 But the problem remains. 但问题仍然存在。 There is still a lag between rendering a window frame and resizing/displaying content surface. 渲染窗口框架和调整大小/显示内容表面之间仍然存在滞后。

However it may solve your problem, as you don't have the border and you will not notice this lag. 但是它可以解决您的问题,因为您没有边框,您不会注意到这种滞后。 But you will have full control over window repaint and it will be made on GPU side. 但是你可以完全控制窗口重绘,它将在GPU端进行。

You have indeed got a nice set of tricks. 你确实有一套很好的技巧。

First I can suggest a few variants on the existing tricks that might help especially on XP/Vista/7, and second I want to mention where the persistent flicker you see on Win8/10 is likely coming from and some tricks to reduce that. 首先,我可以建议一些现有技巧的变种,特别是在XP / Vista / 7上有所帮助,其次我想提一下你在Win8 / 10上看到的持续闪烁可能来自何处以及减少这种变化的一些技巧。

First, despite advice to the contrary in other OP posts, you may find that if you set the CS_HREDRAW|CS_VREDRAW window styles that you avoided before, it actually eliminates the BitBlt that is done inside the internal SetWindowPos Windows does during window resizing (but---and this is the confusing part---on Windows 8/10 you will still see flicker from another source...more on that below). 首先,尽管在其他OP帖子中有相反的建议,你可能会发现如果你设置之前避免的CS_HREDRAW|CS_VREDRAW窗口样式,它实际上消除了在窗口大小调整期间内部SetWindowPos内部完成的BitBlt (但是 - - 这是一个令人困惑的部分 - 在Windows 8/10上你仍会看到来自其他来源的闪烁...更多关于下面的内容。

If you don't want to include CS_HREDRAW|CS_VREDRAW , you can also intercept WM_WINDOWPOSCHANGING (first passing it onto DefWindowProc ) and set WINDOWPOS.flags |= SWP_NOCOPYBITS , which disables the BitBlt inside the internal call to SetWindowPos() that Windows makes during window resizing. 如果你不想包含CS_HREDRAW|CS_VREDRAW ,你也可以拦截WM_WINDOWPOSCHANGING (首先将它传递给DefWindowProc )并设置WINDOWPOS.flags |= SWP_NOCOPYBITS ,这会禁止Windows在窗口内对SetWindowPos()内部调用中的BitBlt大小调整。

Or, you could add to your WM_NCCALCSIZE trick by having it return a set of values that will tell Windows to just BitBlt 1 pixel on top of itself so that even if the BitBlt does happen, it doesn't do anything visibly: 或者,您可以通过返回一组值来添加WM_NCCALCSIZE技巧,这些值将告诉Windows在其自身之上只有BitBlt 1像素,这样即使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;

So that's all very nice, but why does Windows 8/10 look so bad? 所以这一切都非常好,但为什么Windows 8/10看起来如此糟糕?

Apps under Windows 8/10 Aero don't draw directly to the screen, but rather draw to offscreen buffers that are then composited by the evil DWM.exe window manager. Windows 8/10 Aero下的应用程序不直接绘制到屏幕上,而是绘制到屏幕外缓冲区,然后由邪恶的DWM.exe窗口管理器合成。 DWM actually adds another layer of BitBlt -type behavior on top of the existing legacy XP/Vista/7 BitBlt behavior. DWM实际上在现有的旧版XP / Vista / 7 BitBlt行为之上添加了另一层BitBlt类型的行为。

And the DWM blit behavior is even more crazy because they don't just copy the client area, but they actually replicate pixels at the edges of your old client area to make the new one. DWM blit行为更加疯狂,因为它们不仅复制客户区,而且它们实际上复制旧客户区边缘的像素以创建新客户区。 Unfortunately, making DWM not do its blit is much harder than just passing some extra flags. 不幸的是,让DWM不做它的blit要比传递一些额外的标志要困难得多。

I don't have a 100% solution, but I hope the above info will help, and please see this Q&A for a timing trick you can use to reduce the chances of the DWM-layer blit happening to your app: 我没有100%的解决方案,但我希望上面的信息会有所帮助,请参阅此问答,了解可用于减少DWM层blit发生在您应用中的机会的时间技巧:

How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)? 如何在调整窗口大小时平滑丑陋的抖动/闪烁/跳跃,特别是拖动左/上边框(Win 7-10; bg,bitblt和DWM)?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 SetWindowPos()不会移动或调整窗口大小 - SetWindowPos() not moving or resizing window 将窗口状态从最大化更改为还原,而无需移动/调整窗口大小? - Change window status from maximized to restore without moving/resizing the window? FFmpeg捕获窗口时如何避免鼠标指针闪烁? - How to avoid mouse pointer flicker when capture a window by FFmpeg? 失去焦点时窗口闪烁? - Window Flicker on loss of focus? 调整半透明Qt小部件时的黑色闪烁(仅在启用Aero时)? - Black flicker while resizing translucent Qt widget (only when Aero is enabled)? 调整大小时闪烁的白色区域 Qt window - flickering white areas when resizing Qt window 窗口大小时窗口消息WM_SIZING不断发送 - Window message WM_SIZING sending incessantly when window resizing 如何在调整窗口大小时平滑丑陋的抖动/闪烁/跳跃,尤其是拖动左/上边框(Win 7-10;bg、bitblt 和 DWM)? - How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)? 如何在窗口失焦或调整大小时停止暂停OpenGL? - How to stop OpenGL from pausing when the window is out of focus or resizing? 释放鼠标按钮时无法停止调整 window 的大小 - Resizing window cannot be stopped when release mouse button
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM