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



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

原创内容:

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

当用户拖动对话框的左(或右(边缘以调整其大小时,不会闪烁。但是,当我通过调用 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;
}

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

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

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

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

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

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

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

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

最新更新