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;
}
I was fighting with a closely related problem---flicker issues during live resize dragging a window border, which Windows implements internally as a set of
SetWindowPos()
calls.Some of the flicker you mention above with a single child window may be due to two different types of BitBlt.
The first layer applies to all Windows OSes and comes from a
BitBlt
insideSetWindowPos
. You can get rid of thatBitBlt
in several ways. You can create your own custom implementation ofWM_NCCALCSIZE
to tell Windows to blit nothing (or to blit one pixel on top of itself), or alternately you can interceptWM_WINDOWPOSCHANGING
(first passing it ontoDefWindowProc
) and setWINDOWPOS.flags |= SWP_NOCOPYBITS
, which disables theBitBlt
inside the internal call toSetWindowPos()
that Windows makes during window resizing. This has the same eventual effect of skipping theBitBlt
.However, Windows 8/10 aero adds another, more troublesome layer. Apps now draw into an offscreen buffer which is then composited by the new, evil DWM.exe window manager. And it turns out DWM.exe will sometimes do its own
BitBlt
type operation on top of the one already done by the legacy XP/Vista/7 code. And stopping DWM from doing its blit is much harder; so far I have not seen any complete solutions.For sample code that will break through the XP/Vista/7 layer and at least improve the performance of the 8/10 layer, please see:
How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?