0

I am writing a quite simple WinAPI application. Just a window with single button in client area (this is minimum reproducible example). Button creation code is in WM_CREATE main window message handler:

case WM_CREATE:
    hInst = ((LPCREATESTRUCT)lParam)->hInstance;
    hwndButton = CreateWindowW(L"BUTTON", L"Start", WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 
        3, 30, 60, 30, hwnd, NULL, hInst, NULL);
    break;

In real application I have to update window content when user moves mouse over window and when user resizes window. All update here is just filling client area with single color:

case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);
    FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW + 10));
    EndPaint(hwnd, &ps);
    return 0

WM_size and WM_MOUSEMOVE message handlers just invalidate all client area:

case WM_SIZE:
    InvalidateRect(hwnd, NULL, TRUE);
    return 0;
    
case WM_MOUSEMOVE:
    InvalidateRect(hwnd, NULL, TRUE);
    break;

The problem is, behaviour of application differs in case of resizing window and in case of moving mouse pointer over it. In the first case, nothing happens except of window resizing - just as I want. But in second case, when mouse pointer moves over window, window begins to flicker. It's annoying. Moreover, button in client area also disappears - and appears again only after mouse pointer stops. This is absolutely unacceptable. What can I do to fix this situation?

Full text of program, if it will be useful:

#include <tchar.h>
#include <windows.h>

#if defined(UNICODE) && !defined(_UNICODE)
    #define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
    #define UNICODE
#endif

HINSTANCE hInst;

/*  Make the class name into a global variable  */
WCHAR szClassName[ ] = L"WindowsApp";

LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*Application entry point. Function made from template. */
int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd, hWndToolbar;     /* This is the handle for our window */
    MSG messages;       /* Here messages to the application are saved */
    WNDCLASSEXW wincl;          /* Data structure for the windowclass */

    hInst = hThisInstance;
    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = NULL;
    wincl.hIconSm = wincl.hIcon; //LoadIcon(NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassExW (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowExW (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           L"Check button",     /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           CW_USEDEFAULT,       /* The programs width */
           CW_USEDEFAULT,       /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* Class menu used */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}

/* Message handler: */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND hwndButton;
    PAINTSTRUCT ps;
    HDC hdc;
    
    switch (message)                  /* handle the messages */
    { 
        case WM_CREATE:
            hInst = ((LPCREATESTRUCT)lParam)->hInstance;
            hwndButton = CreateWindowW(L"BUTTON", L"Start", WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 
                3, 30, 60, 30, hwnd, NULL, hInst, NULL);
            break;

        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW + 10));
            EndPaint(hwnd, &ps);
            return 0;
        
        case WM_SIZE:
            InvalidateRect(hwnd, NULL, TRUE);
            return 0;
            
        case WM_MOUSEMOVE:
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;

        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}
  • 3
    To solve the button disappearing issue, add the `WS_CLIPCHILDREN` style when creating the main window. – IInspectable Jul 17 '22 at 11:33
  • Thank you very much! WS_CLIPCHILDREN is what I did not know. Button is always visible now. But maybe there is something I can to do with flickering? – Андрей Гиль Jul 17 '22 at 12:05
  • 4
    Add a handler for `WM_ERASEBKGND` and return 0. At the moment the default handler is painting your window with `COLOR_BACKGROUND` and then you're repainting it with `COLOR_WINDOW`. – Jonathan Potter Jul 17 '22 at 12:59
  • @JonathanPotter correction - at the moment `WM_ERASEBKGND` is painting the window with `COLOR_BACKGROUND-1` because OP forgot the +1. – dialer Jul 17 '22 at 13:33
  • @dialer please clarify who is OP and where should be forgotten +1? – Андрей Гиль Jul 17 '22 at 14:46
  • @АндрейГиль You are using the `COLOR_` constants for `hbrBackground` wrong. You must add +1 to the constant value. If you want `COLOR_BACKGROUND`, you must specify `wincl.hbrBackground = COLOR_BACKGROUND + 1`. The reason is... ugh... I don't rember clearly enough. But the problem is the first `COLOR_` value is `0`. Which must be differentiated from `0` ("no brush"). So they just defined it so that you have to add 1 to make a "valid" (non-zero) value from all `COLOR_` constants. – dialer Jul 17 '22 at 15:32
  • OK memory refreshed. It is what I thought at first. There are other APIs that use the same `COLOR_` constants. Like `GetSysColor` or `GetSysColorBrush`. When calling those APIs, use the constants directly; do *not* add +1. But at some point `hbrBackground` came and wanted to use the same `COLOR_` constants for convenience. `COLOR_SCROLLBAR` is defined as `0`, but to `hbrBackground`, `0` means "null brush". So `COLOR_BACKGROUND` interprets the color value differently, it expects it to be offset by 1 to be able to differentiate between `0` and `COLOR_SCROLLBAR`. MS could have introduced a macro. – dialer Jul 17 '22 at 15:42
  • 1
    Anyway this is totally not relevant to your question because you need to *bypass* clearing via `WM_ERASEBKGND` to avoid flicker (that's what Jonathan suggested). So the brush will only get used exactly one time: When the buffer for your window is initially created when your window is created for the first time (this is a bit weird but you cannot avoid the initial fill in Win10 even if you specifiy null brush - in Win7 it was possible). After that, your `WM_PAINT` will handle everything else, and the `hbrBackground` will never be used again. This is WAY more details than you'll ever need. – dialer Jul 17 '22 at 15:45
  • 1
    You can also read [Flicker-Free Displays Using an Off-Screen DC](https://learn.microsoft.com/en-us/previous-versions/ms969905(v=msdn.10)) for more information. – YangXiaoPo-MSFT Jul 18 '22 at 01:33

0 Answers0