Reduce flickering when using SetWindowPos to change the left edge of a window

0

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;
}

Flicker-free expansion (resize) of a window to the left

Flickering on window when resizing from left side

winapi
resize
flicker
asked on Stack Overflow Jun 17, 2018 by Kenny Liu • edited Jun 14, 2019 by TylerH

1 Answer

0

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 inside SetWindowPos. You can get rid of that BitBlt in several ways. You can create your own custom implementation of WM_NCCALCSIZE to tell Windows to blit nothing (or to blit one pixel on top of itself), or alternately you can intercept WM_WINDOWPOSCHANGING (first passing it onto DefWindowProc) and set WINDOWPOS.flags |= SWP_NOCOPYBITS, which disables the BitBlt inside the internal call to SetWindowPos() that Windows makes during window resizing. This has the same eventual effect of skipping the BitBlt.

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)?

answered on Stack Overflow Oct 26, 2018 by Louis Semprini • edited Oct 26, 2018 by Louis Semprini

User contributions licensed under CC BY-SA 3.0