Winapi 使用SetWindowPos更改窗口左边缘时减少闪烁

Winapi 使用SetWindowPos更改窗口左边缘时减少闪烁,winapi,resize,flicker,Winapi,Resize,Flicker,更新1:以下是简化版: 因此,我有一个特殊的固定大小的子窗口,我希望它保持在可调整大小的主窗口的右侧。当用户通过拖动主窗口的左/右边缘来调整主窗口的大小时,会发送WM_WINDOWPOSCHANGED,子窗口将在此消息处理程序中移动,使其“粘”到右侧,并且在发生这种情况时不会闪烁 但是,当我试图通过SetWindowPos以编程方式调整主窗口的大小时,会出现明显的闪烁。似乎操作系统将旧内容复制到新的客户端区域,甚至在我有机会在WM_WINDOWPOSCHANGED中处理子窗口重新定位之前。以下是

更新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
这是可复制的伪代码。要测试它,您可以通过VisualStudio的新建项目向导创建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更改主窗口的右边缘时,不会发生闪烁

原始内容:

假设我有一个对话框,上面有两个列表控件,我希望左边的对话框随着对话框一起调整大小,但右边的对话框大小不变。

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

我知道这个问题以前也提过,建议可以帮忙。虽然它的文档看起来确实是要走的路,但我仍然无法让它解决闪烁的问题

代码基本上如下所示。我还在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;
}
BOOL g_bexpandingshopping=FALSE;
CommandExpandShrinkWindow(HWND HWND,BOOL bExpand)上的空隙
{
RECT-RECT;
GetWindowRect(hWnd和&rect);
rect.left+=bExpand?-100:100;
UINT nFlags=SWP_NOZORDER | SWP_NOACTIVATE;
g_BexpandingShring=真;
SetWindowPos(hWnd、nullptr、rect.left、rect.top、rect.right-rect.left、rect.bottom-rect.top、nFlags);
g_BexpandingShring=假;
}
LRESULT OnNcCalcSize(HWND HWND,WPARAM WPARAM,LPARAM LPARAM)
{
NCCALCSIZE_参数*lpncsp=重新解释强制转换(LPRAM);
勒苏尔群岛;
if(wParam和&g_bexpandingShring)
{
//让DefWindowProc计算新的客户端矩形
res=DefWindowProc(hWnd、WM\NCCALCSIZE、wParam、lParam);
//复制右列表控件的内容
GetWindowRect(g_HwnDiskRight,lpncsp->rgrc+2);
lpncsp->rgrc[1]=lpncsp->rgrc[2];
res=WVR_有效值;
}
其他的
{
res=DefWindowProc(hWnd、WM\NCCALCSIZE、wParam、lParam);
}
返回res;
}


我一直在努力解决一个密切相关的问题——实时调整窗口边框大小时出现的闪烁问题,Windows在内部通过一组
SetWindowPos()
调用来实现

上面提到的单个子窗口闪烁可能是由两种不同类型的BitBlt引起的

第一层适用于所有Windows操作系统,并且来自
SetWindowPos
内部的
BitBlt
。您可以通过多种方式消除该
BitBlt
。您可以创建自己的
WM\u NCCALCSIZE
自定义实现,告诉Windows什么也不闪烁(或在其顶部闪烁一个像素),或者您也可以截取
WM\u WINDOWPOSCHANGING
(首先将其传递到
DefWindowProc
)并设置
WINDOWPOS.flags
,这将禁用Windows在调整窗口大小期间对
SetWindowPos()
进行的内部调用中的
BitBlt
。这与跳过
BitBlt
的最终效果相同

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

有关突破XP/Vista/7层并至少提高8/10层性能的示例代码,请参阅:


您应该发布MCVE。不是你在github上发布的项目,那个项目太大了。你不需要处理WM\u NCCALCSIZE来防止闪烁,据我所知。MCVE会非常有用。建议:如果要“同时”更改多个子窗口的大小、位置或可见性,请使用
DeferWindowPos()
而不是
SetWindowPos()
。这样,系统可以在单个批处理操作中进行重绘,而不是一次重绘一个子窗口,从而导致闪烁。好吧,我现在设法简化了这个问题。事实上,即使只有一个子窗口,您也可以重现此问题,因此DeferreWindowPos在这方面没有多大帮助。msdn中的Viorel提供了一种解决缩小问题的方法,即提前移动子窗口,它确实减少了闪烁,遗憾的是,扩大窗口后仍会闪烁。