使用SetWindowPos何时改变窗口的左边减少闪烁(Reduce flickering when

2019-10-30 07:02发布

更新1:这里的简化版本:

所以,我有,我希望把它留在可调整大小的主窗口的右侧 ,一个特殊的固定大小的子窗口。 当用户调整通过拖动它的左/右边缘的主窗口,WM_WINDOWPOSCHANGED发送后,子窗口将在此消息处理移动,使得它“大棒”到右侧,而且没有闪烁,当发生这种情况。

然而,当我试图以编程方式调整由SetWindowPos主窗口中,有明显的闪烁。 看来,OS复制旧的内容到新的客户区,甚至在我有机会来处理子窗口重新定位在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桌面应用程序项目,然后将这些代码复制到适当的位置。 闪烁的发生是因为OS“的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主窗口的右边缘。

原创内容:

比方说,我有上有两个列表控件的对话,我想在对话框左侧的一个调整大小一起,但正确的大小保持不变。 无闪烁时手动调整大小

有当用户拖动对话框的左(或右)边调整其大小无闪烁。 但是,我这样做编程方式通过调用SetWindowPos时,会出现明显的闪烁。 看来的Windows拷贝内容保存到窗口前甚至被送到WM_SIZE。

SetWindowPos产生闪烁

我知道这个问题之前已经提出来了, 有些人认为, WM_NCCALCSIZE可以提供帮助。 虽然它的文件确实似乎是要走的路,我仍然无法得到它解决了闪烁。

该代码基本上看起来像下面这样。 我也投入了示范项目在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;
}

无闪烁的窗口的膨胀(调整大小)向左

从左侧调整时,闪烁的窗口

Answer 1:

我当时在现场调整尺寸密切相关的问题---战斗闪烁问题拖动窗口边框,哪个Windows内部实现为一组SetWindowPos()调用。

有些你提到上面一个子窗口闪烁,可能是由于两种不同类型的BitBlt的。

第一层适用于所有Windows操作系统和来自一个BitBlt内部SetWindowPos 。 你可以摆脱的BitBlt几种方式。 你可以创建自己的自定义实现WM_NCCALCSIZE告诉Windows启动的blit没有(或做块在其本身上一个像素),或交替您可以拦截WM_WINDOWPOSCHANGING (第一传递到DefWindowProc ),并设置WINDOWPOS.flags |= SWP_NOCOPYBITS ,这禁用BitBlt内部呼叫内部SetWindowPos()的Windows窗口大小调整期间进行。 这有跳过的同样效果最终BitBlt

但是,Windows 8/10航空又增加了,更麻烦的层。 现在应用吸引到屏幕外缓冲区,然后由新的,邪恶的DWM.exe窗口管理器合成。 而事实证明DWM.exe有时会做自有BitBlt上已经由传统的XP / Vista / 7的代码做了一个顶部型操作。 而且从做它的块传输停止DWM更难; 到目前为止,我还没有看到任何完整的解决方案。

对于示例代码,将通过XP / Vista / 7的层断裂,至少提高了8/10层的性能,请参阅:

(; BG,BitBlt和DWM赢7-10)如何调整窗口大小时,尤其是拖动左/上边框顺利丑陋的抖动/闪烁/跳跃?



文章来源: Reduce flickering when using SetWindowPos to change the left edge of a window