减少使用 SetWindowPos 更改窗口左边缘时的闪烁 [英] Reduce flickering when using SetWindowPos to change the left edge of a window

查看:165
本文介绍了减少使用 SetWindowPos 更改窗口左边缘时的闪烁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新 1:这是简化版本:

所以我有一个特殊的固定大小的子窗口,我想让它留在可调整大小的主窗口的右侧.当用户通过拖动主窗口的左/右边缘来调整主窗口的大小时,发送 WM_WINDOWPOSCHANGED,子窗口将在此消息处理程序中移动,使其粘"到右侧,并且发生这种情况时不会闪烁.

So I have a special fixed-size child window that I want to make it stay at the right side of the resizable main window. When users resize the main window by dragging the left/right edge of it, WM_WINDOWPOSCHANGED is sent, the child window will be moved in this message handler so that it "sticks" to the right side, and there is no flickering when this happens.

但是,当我尝试通过 SetWindowPos 以编程方式调整主窗口的大小时,会出现明显的闪烁.似乎操作系统将旧内容复制到新客户区,甚至在我有机会在 WM_WINDOWPOSCHANGED 中处理子窗口重新定位之前.这是在 SetWindowPos 和 WM_SIZE 之间发送的消息:

However, when I try to programmatically resize the main window by SetWindowPos, there is noticeable flickering. It seems that the OS copies the old content to the new client area, even before I have a chance to handle the child window repositioning in WM_WINDOWPOSCHANGED. Here's the messages dispatched between SetWindowPos and WM_SIZE:

WndProc: 0x00000046 WM_WINDOWPOSCHANGING
WndProc: 0x00000024 WM_GETMINMAXINFO
WndProc: 0x00000083 WM_NCCALCSIZE
WndProc: 0x00000093 WM_UAHINITMENU
===Flickering happens between these two messages!===
WndProc: 0x00000085 WM_NCPAINT
WndProc: 0x00000093 WM_UAHINITMENU
WndProc: 0x00000093 WM_UAHINITMENU
WndProc: 0x00000091 WM_UAHDRAWMENU
WndProc: 0x00000092 WM_UAHDRAWMENUITEM
WndProc: 0x00000092 WM_UAHDRAWMENUITEM
WndProc: 0x00000092 WM_UAHDRAWMENUITEM
WndProc: 0x00000014 WM_ERASEBKGND
WndProc: 0x00000047 WM_WINDOWPOSCHANGED
WndProc: 0x00000003 WM_MOVE
WndProc: 0x00000005 WM_SIZE

这是可重现的伪代码.要测试它,您可以通过 Visual Studio 的新项目向导创建一个 Windows 桌面应用程序项目,然后将这些代码复制到适当的位置.发生闪烁是因为操作系统BitBlt"将旧内容(由于左侧没有其他子窗口而为白色背景)转移到新客户区.如果在主窗口的左侧创建另一个子窗口,闪烁会更明显.

Here's the reproducible pseudo-code. To test it, you can create a Windows Desktop Application project via Visual Studio's new project wizard, then copy these code to the proper place. The flickering happens because the OS "BitBlt" the old content (which is white background since there is no other child windows at the left side) to the new client area. The flickering will be more noticeable if you create another child window at the left side of the main window.

HWND g_hWndList = NULL;
#define LIST_WIDTH  500
#define LIST_HEIGHT 400

void GetListRect(HWND hWnd, RECT& rectList)
{
    GetClientRect(hWnd, &rectList);
    InflateRect(&rectList, -10, -10);
    rectList.left = rectList.right - LIST_WIDTH;
    rectList.bottom = rectList.top + LIST_HEIGHT;
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, ...);
   RECT rectList;
   GetListRect(hWnd, rectList);
   g_hWndList = CreateWindow(WC_LISTVIEW, TEXT("listR"), WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | LVS_REPORT,
       rectList.left, rectList.top, rectList.right - rectList.left, rectList.bottom - rectList.top, hWnd, nullptr, hInstance, nullptr);

   ...
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            switch (wmId)
            {
            case IDM_ABOUT:
                // Resize the window instead of showing "About" dialog
                //DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                {
                    RECT rect;
                    GetWindowRect(hWnd, &rect);
                    rect.left += 100; // make it smaller
                    SetWindowPos(hWnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
                }
                break;
            }
        }
        break;
    case WM_WINDOWPOSCHANGED:
        {
            RECT rectList;
            GetListRect(hWnd, rectList);
            SetWindowPos(g_hWndList, nullptr, rectList.left, rectList.top, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
        }
        break;
    }
}

注意:仅使用 SetWindowPos 更改主窗口的右边缘时不会发生闪烁.

Note: the flickering won't happen when you only change the right edge of the main window with SetWindowPos.

原创内容:

假设我有一个对话框,上面有两个列表控件,我希望左边的控件与对话框一起调整大小,但右边的控件大小保持不变.手动调整大小时无闪烁

Let's say I have a dialog with two list controls on it, and I want the left one resizes along with the dialog, but the right one remains the same size. No flickering when manually resizes

当用户拖动对话框的左(或右)边缘以调整其大小时,没有闪烁.但是,当我通过调用 SetWindowPos 以编程方式执行此操作时,会出现明显的闪烁.似乎 Windows 甚至在发送 WM_SIZE 之前将保存的内容复制到窗口中.

There is no flickering when users drag the left (or right) edge of the dialog to resize it. However, when I do this programmatically by calling SetWindowPos, there will be noticeable flickering. It seems that Windows copy the saved content to the window before WM_SIZE is even sent.

SetWindowPos 产生闪烁

我知道之前已经提出过这个问题,有些人 建议 WM_NCCALCSIZE 可以提供帮助.虽然它的文档似乎确实是要走的路,但我仍然无法解决闪烁问题.

I am aware that this issue has been brought up before, some people suggest that WM_NCCALCSIZE can help. Although the document of it indeed seems to be the way to go, I still couldn't get it to solve the flickering.

代码基本上如下所示.我还在 github 上放了一个 演示项目.

The code basically looks like the following. I have also put a demo project on github.

我在这里做错了什么?

BOOL g_bExpandingShrinking = FALSE;

void OnCommandExpandShrinkWindow(HWND hWnd, BOOL bExpand)
{
    RECT rect;
    GetWindowRect(hWnd, &rect);
    rect.left += bExpand ? -100 : 100;
    UINT nFlags = SWP_NOZORDER | SWP_NOACTIVATE;
    g_bExpandingShrinking = TRUE;
    SetWindowPos(hWnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, nFlags);
    g_bExpandingShrinking = FALSE;
}

LRESULT OnNcCalcSize(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    NCCALCSIZE_PARAMS* lpncsp = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
    LRESULT res;
    if (wParam && g_bExpandingShrinking)
    {
        // let DefWindowProc calculate the new client rectangle
        res = DefWindowProc(hWnd, WM_NCCALCSIZE, wParam, lParam);
        // copy the content of the right list control
        GetWindowRect(g_hwndListRight, lpncsp->rgrc + 2);
        lpncsp->rgrc[1] = lpncsp->rgrc[2];
        res = WVR_VALIDRECTS;
    }
    else
    {
        res = DefWindowProc(hWnd, WM_NCCALCSIZE, wParam, lParam);
    }
    return res;
}

无闪烁扩展(调整大小)左边的窗口

从左侧调整大小时窗口闪烁

推荐答案

我正在与一个密切相关的问题作斗争 --- 在拖动窗口边框时实时调整大小的闪烁问题,Windows 在内部将其作为一组 SetWindowPos 实现() 调用.

I was fighting with a closely related problem---flicker issues during live resize dragging a window border, which Windows implements internally as a set of SetWindowPos() calls.

您在上面提到的单个子窗口的某些闪烁可能是由于两种不同类型的 BitBlt.

Some of the flicker you mention above with a single child window may be due to two different types of BitBlt.

第一层适用于所有 Windows 操作系统,来自 SetWindowPos 内的 BitBlt.您可以通过多种方式摆脱那个 BitBlt.您可以创建自己的 WM_NCCALCSIZE 自定义实现,以告诉 Windows 不进行任何 blit(或 blit 自身顶部的一个像素),或者您也可以拦截 WM_WINDOWPOSCHANGING(第一次通过到 DefWindowProc) 并设置 WINDOWPOS.flags |= SWP_NOCOPYBITS,这会禁用 SetWindowPos()<的内部调用中的 BitBlt/code> Windows 在调整窗口大小时生成的.这与跳过 BitBlt 的最终效果相同.

The first layer applies to all Windows OSes and comes from a BitBlt inside SetWindowPos. You can get rid of that BitBlt in several ways. You can create your own custom implementation of WM_NCCALCSIZE to tell Windows to blit nothing (or to blit one pixel on top of itself), or alternately you can 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. This has the same eventual effect of skipping the BitBlt.

然而,Windows 8/10 aero 增加了另一个更麻烦的层.应用程序现在绘制到一个屏幕外缓冲区,然后由新的、邪恶的 DWM.exe 窗口管理器合成.事实证明,DWM.exe 有时会在旧版 XP/Vista/7 代码已经完成的操作之上执行自己的 BitBlt 类型操作.阻止 DWM 执行它的 blit 要困难得多;到目前为止,我还没有看到任何完整的解决方案.

However, Windows 8/10 aero adds another, more troublesome layer. Apps now draw into an offscreen buffer which is then composited by the new, evil DWM.exe window manager. And it turns out DWM.exe will sometimes do its own BitBlt type operation on top of the one already done by the legacy XP/Vista/7 code. And stopping DWM from doing its blit is much harder; so far I have not seen any complete solutions.

突破XP/Vista/7层,至少提升8/10层性能的示例代码,请看:

For sample code that will break through the XP/Vista/7 layer and at least improve the performance of the 8/10 layer, please see:

如何平滑丑陋的抖动调整窗口大小时/闪烁/跳跃,尤其是拖动左/上边框(Win 7-10;bg、bitblt 和 DWM)?

这篇关于减少使用 SetWindowPos 更改窗口左边缘时的闪烁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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