Уменьшите мерцание при использовании 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
Вот воспроизводимый псевдокод. Чтобы проверить это, вы можете создать проект Windows Desktop Application с помощью мастера новых проектов Visual Studio, а затем скопировать этот код в нужное место. Мерцание происходит из-за того, что ОС "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;
}
1 ответ
Я боролся с тесно связанной проблемой - проблемы мерцания во время изменения размера при перетаскивании границы окна, которые Windows реализует внутри себя как набор SetWindowPos()
звонки.
Некоторые из мерцаний, которые вы упомянули выше с одним дочерним окном, могут быть вызваны двумя различными типами BitBlt.
Первый уровень применяется ко всем ОС Windows и происходит от BitBlt
внутри SetWindowPos
, Вы можете избавиться от этого BitBlt
несколькими способами. Вы можете создать свою собственную реализацию WM_NCCALCSIZE
чтобы сказать Windows, чтобы ничего не стряхивать (или отбрасывать один пиксель поверх себя), или вы можете перехватить WM_WINDOWPOSCHANGING
(сначала передавая его на DefWindowProc
) и установить WINDOWPOS.flags |= SWP_NOCOPYBITS
, который отключает BitBlt
внутри внутреннего звонка SetWindowPos()
что Windows делает во время изменения размера окна. Это имеет тот же возможный эффект пропуска BitBlt
,
Тем не менее, Windows 8/10 Aero добавляет еще один, более хлопотный слой. Приложения теперь рисуют в закадровом буфере, который затем создается новым, злым оконным менеджером DWM.exe. И оказывается, что DWM.exe иногда делает свое дело BitBlt
введите операцию поверх той, что уже была сделана в устаревшем коде XP/Vista/7. А помешать DWM делать это намного сложнее; до сих пор я не видел каких-либо полных решений.
Для примера кода, который пробьет уровень XP/Vista/7 и по крайней мере улучшит производительность уровня 8/10, пожалуйста, смотрите: