移动/调整窗口大小时闪烁 [英] Flicker when moving/resizing window

查看:687
本文介绍了移动/调整窗口大小时闪烁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经开发了一个显示jpeg图像的应用程序.它可以显示4个图像,每个屏幕象限一个.它使用4个窗口.窗口没有边框(框架)或标题栏. 加载新图像后,将调整新图像的窗口大小,然后显示该图像.

I have developed an application to display jpeg images. It can display 4 images, one in each quadrant of the screen. It uses 4 windows for that. 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:

  • 该窗口仅具有样式CS_DBLCLKS(没有样式CS_HREDRAWCS_VREDRAW);

背景笔刷为NULL;

the background brush is NULL;

WM_ERASEBKGND返回1;

WM_NCPAINT返回0;

WM_NCCALCSIZE告诉要对齐到未移动的那一侧(可以告诉它丢弃客户区域吗?);

WM_NCCALCSIZE tells to align to the side not moved (can you tell it to discard the client area?);

WM_WINDOWPOSCHANGING返回0;

SetWindowPos具有标志SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING.

SetWindowPos has flags 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设置为新的大小和位置;

  • SetWindowPos to new size and position;

InvalidateRect(hWnd,NULL,FALSE);

InvalidateRect (hWnd, NULL, FALSE);

UpdateWindow(hWnd);

UpdateWindow(hWnd);

在进行绘画,背景擦除或移动内容之前,不进行任何操作,直到WM_PAINT.

without any painting, background erasing or content moving until 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);

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

Can anyone tell me if/where I make a mistake that causes the old content of the window to be moved?

硬件等:带有NVIDIA Quadro K1000m驱动程序9.18.13.3265的HP Elitebook Core7 64位Windows 7(更新为341.44).


更新(17年7月)

Hardware etc: Windows 7 on HP Elitebook Core7 64 bits with NVIDIA Quadro K1000m driver 9.18.13.3265 (updated to 341.44).


UPDATE (Jul '17)

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

I have seen the behavior of the pogram also on another Windows computer (Windows 8/10). It does not seem to be the NVIDIA display driver.

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

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).

我可能对WM_NCCALCSIZE消息的计算有问题,以告诉Windows不要执行任何操作.有人可以举例说明我的目的吗?另请参见

I may have problems with the calculations for the WM_NCCALCSIZE message to tell Windows not to do anything. 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?

推荐答案

您确实有很多技巧.

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

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.

首先,尽管在其他OP帖子中提出了相反的建议,但您可能会发现,如果设置了以前避免使用的CS_HREDRAW|CS_VREDRAW窗口样式,则实际上消除了在内部SetWindowPos Windows内部完成的BitBlt在调整窗口大小时会执行此操作(但是-这是令人困惑的部分-在Windows 8/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).

如果不想包含CS_HREDRAW|CS_VREDRAW,则还可以拦截WM_WINDOWPOSCHANGING(首先将其传递到DefWindowProc)并设置WINDOWPOS.flags |= SWP_NOCOPYBITS,这会将内部调用中的BitBlt禁用为 Windows在调整窗口大小时所做的.

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.

或者,您可以添加WM_NCCALCSIZE技巧,使其返回一组值,这些值将告诉Windows在其顶部仅BitBlt 1个像素,这样即使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:

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;

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

So that's all very nice, but why does Windows 8/10 look so bad?

Windows 8/10 Aero下的应用程序不会直接绘制到屏幕上,而是绘制到屏幕外缓冲区,然后由邪恶的DWM.exe窗口管理器合成.实际上,DWM在现有的旧版XP/Vista/7 BitBlt行为的基础上又添加了另一层BitBlt类型的行为.

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. DWM actually adds another layer of BitBlt-type behavior on top of the existing legacy XP/Vista/7 BitBlt behavior.

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

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. Unfortunately, making DWM not do its blit is much harder than just passing some extra flags.

我没有100%的解决方案,但希望以上信息对您有所帮助,请参阅此问题与解答以获取计时技巧,以减少DWM层发生在您的应用中的可能性:

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:

如何消除丑陋的抖动/flicker/jumping调整窗口大小时,尤其是拖动左/上边框(Win 7-10; bg,bitblt和DWM)?

这篇关于移动/调整窗口大小时闪烁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆