【问题标题】:Reduce flickering when using SetWindowPos to change the left edge of a window使用 SetWindowPos 更改窗口左边缘时减少闪烁
【发布时间】:2018-11-26 16:20:57
【问题描述】:

更新 1:这是简化版:

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

但是,当我尝试通过 SetWindowPos 以编程方式调整主窗口大小时,会出现明显的闪烁。似乎操作系统将旧内容复制到新客户区,甚至在我有机会处理 WM_WINDOWPOSCHANGED 中的子窗口重新定位之前。下面是 SetWindowPos 和 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”将旧内容(由于左侧没有其他子窗口而为白色背景)到新客户区。如果您在主窗口的左侧创建另一个子窗口,闪烁会更加明显。

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 更改主窗口的右边缘时不会发生闪烁。

原创内容:

假设我有一个对话框,上面有两个列表控件,我希望左边的一个与对话框一起调整大小,但右边的大小保持不变。 No flickering when manually resizes

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

SetWindowPos produces flickering

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

代码基本上如下所示。我还在github上放了demo project

我在这里做错了什么?

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;
}

Flicker-free expansion (resize) of a window to the left

Flickering on window when resizing from left side

【问题讨论】:

  • 您应该发布 MCVE。不是你在 github 上发布的项目,那个项目太大了。据我所知,您不需要处理WM_NCCALCSIZE 来防止闪烁。
  • MCVE 真的很有帮助。只是一个建议:如果您想“一次”更改多个子窗口的大小、位置或可见性,请使用 DeferWindowPos() 而不是 SetWindowPos()。这样系统可以在一个批处理操作中进行重绘,而不是一次重绘一个子窗口,导致闪烁。见"What’s the point of DeferWindowPos?"
  • 好的,我现在设法简化了这个问题。实际上,即使只有一个子窗口,您也可以重现此问题,因此 DeferWindowPos 在这里没有多大帮助。
  • Viorel_ from msdn forum 提供了一种解决缩小情况的办法,就是提前移动子窗口,确实减少了闪烁,可惜扩大窗口还是闪烁。

标签: winapi resize flicker


【解决方案1】:

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

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

第一层适用于所有 Windows 操作系统,来自BitBlt 内的SetWindowPos。您可以通过多种方式摆脱 BitBlt。您可以创建自己的 WM_NCCALCSIZE 自定义实现来告诉 Windows 什么都不传输(或在自身顶部传输一个像素),或者您可以拦截 WM_WINDOWPOSCHANGING(首先将其传递给 DefWindowProc)并设置 @987654329 @,它会禁用 Windows 在调整窗口大小期间对 SetWindowPos() 的内部调用中的 BitBlt。这与跳过BitBlt 具有相同的最终效果。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-26
    • 2010-10-23
    • 2010-09-16
    • 2022-10-13
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    • 2011-03-27
    相关资源
    最近更新 更多