Update 1: Here's the simplified version:
So I have a special fixed-size child window that I want to make it stay at the right side of the resizable main window. When users resize the main window by dragging the left/right edge of it, WM_WINDOWPOSCHANGED is sent, the child window will be moved in this message handler so that it "sticks" to the right side, and there is no flickering when this happens.
However, when I try to programmatically resize the main window by SetWindowPos, there is noticeable flickering. It seems that the OS copies the old content to the new client area, even before I have a chance to handle the child window repositioning in WM_WINDOWPOSCHANGED. Here's the messages dispatched between SetWindowPos and 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
Here's the reproducible pseudo-code. To test it, you can create a Windows Desktop Application project via Visual Studio's new project wizard, then copy these code to the proper place. The flickering happens because the OS "BitBlt" the old content (which is white background since there is no other child windows at the left side) to the new client area. The flickering will be more noticeable if you create another child window at the left side of the main window.
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;
}
}
Note: the flickering won't happen when you only change the right edge of the main window with SetWindowPos.
Original content:
Let's say I have a dialog with two list controls on it, and I want the left one resizes along with the dialog, but the right one remains the same size. No flickering when manually resizes
There is no flickering when users drag the left (or right) edge of the dialog to resize it. However, when I do this programmatically by calling SetWindowPos, there will be noticeable flickering. It seems that Windows copy the saved content to the window before WM_SIZE is even sent.
SetWindowPos produces flickering
I am aware that this issue has been brought up before, some people suggest that WM_NCCALCSIZE can help. Although the document of it indeed seems to be the way to go, I still couldn't get it to solve the flickering.
The code basically looks like the following. I have also put a demo project on github.
What have I done wrong here?
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;
}
WM_NCCALCSIZE
to prevent flicker, not as far as I know. – LivelyDeferWindowPos()
instead ofSetWindowPos()
. This way the system can do redrawing in a single batch operation, instead of redrawing one child window at a time, causing flickering. See "What’s the point of DeferWindowPos?" – IsaacisaacsWS_EX_LAYERED
andWS_EX_COMPOSITED
and that fixed the flickering. – Lange