10

The following is taken from the Remarks section of the MoveWindow() documentation:

If the bRepaint parameter is TRUE, the system sends the WM_PAINT message to the window procedure immediately after moving the window (that is, the MoveWindow function calls the UpdateWindow function).

So I assumed that when I call MoveWindow() with bRepaint set to TRUE, the window procedure will be called immediately and passed a WM_PAINT message, but this is what my testing shows:

  • When MoveWindow() is called, the window procedure is called immediately, but a WM_ERASEBKGND message is passed to it and not a WM_PAINT message.
  • The region is still invalid and so when I go back to the message loop and no messages are in the message queue, a WM_PAINT message is sent.

Did I interpret the documentation wrong?

Note: I am talking about calling the MoveWindow() method on the parent window object.


Edit:

This is my test code:

/* Left mouse click on the window to call MoveWindow() */

#include <Windows.h>

HWND hEdit;

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case WM_LBUTTONDOWN:
        MoveWindow(hWnd, 200, 200, 700, 700, TRUE);

        // Do not go back to message loop immediately
        Sleep(3000);
        break;
    case WM_ERASEBKGND:
        {
            SendMessage(hEdit, WM_CHAR, (WPARAM)'e', 0);
        }
        break;
    case WM_PAINT:
        {
            SendMessage(hEdit, WM_CHAR, (WPARAM)'p', 0);

            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);

            EndPaint(hWnd, &ps);
        }
        break;
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "WinClass";
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    RegisterClassEx(&wc);

    HWND hWnd = CreateWindowEx(0, "WinClass", "", WS_OVERLAPPEDWINDOW, 261, 172, 594, 384, NULL, NULL, hInstance, NULL);
    hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, 0, 0, 400, 21, hWnd, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
  • Can we see a simple example code working as you've described? – cdonts Oct 10 '15 at 01:47
  • 1
    It's quite normal for `WM_ERASEBKGND` to precede `WM_PAINT` as part of a standard repaint cycle, the docs are just not detailed enough. – Jonathan Potter Oct 10 '15 at 01:53
  • @Jonathan Potter I am talking about the fact that the window procedure is called directly for `WM_ERASEBKGND`, but it is not called directly for `WM_PAINT`. The documentation says: *"the MoveWindow function calls the UpdateWindow function"*, so this means that the window procedure should be called directly for `WM_PAINT`, but this is not happening! –  Oct 10 '15 at 03:11
  • 2
    You normally *always* pass TRUE, you don't care how painting happens. The argument is there simply to allow you to optimize the special case where you move a bunch of windows around. Not the case here. – Hans Passant Oct 10 '15 at 22:42
  • it is preferably to trace the code using OutputDebugString instead of SendMessage(hEdit, WM_CHAR, (WPARAM)'p', 0); use DebugView from SysInternals to capture the output – milevyo Oct 12 '15 at 20:17
  • It's possible that the DWM introduced in Vista has changed how this works. Windows are rendered off-screen and composited, so Windows now has a record of the contents of a window. It's possible that when moving a window these days it's able to optimise by using the cached contents rather than re-rendering. Who knows. Microsoft don't document stuff to that level any more. – Jonathan Potter Oct 14 '15 at 11:41

4 Answers4

8

Did I interpret the documentation wrong?

Basically, yes. You discovered a little fact about MSDN documentation on winapi functions that is very, very important to know. It is not written to be a tutorial. It assumes a basic understanding of how the winapi works, the kind of knowledge you get from reading Petzold's "Programming Windows" book.

That book can teach you that the Windows painting cycle always includes WM_ERASEBKGND. So the background is painted first, what ever you draw on top of it with WM_PAINT is next.

Several reasons why such implementation details are skipped in the MSDN documentation. First off, there is a lot of it and including everything just makes it hard to plow through the article. Next, it is pretty unusual to actually write a message handler for WM_ERASEBKGND. You normally just pass it on to DefWindowProc(). Which uses the WNDCLASSEX.hbrBackground you selected, 99% of the time good enough to get the job done. Note how your window looks screwed up because that's what you did not do. Since you wrote a message handler, it is now your job to take care of it. Easy to do, just call DefWindowProc() yourself.

Finally, MSDN documentation omits details because nailing them down makes it very difficult to ever improve the way Windows works. There's another implementation detail that you can see from your test program. Quite often, calling MoveWindow with bPaint = TRUE does not paint anything at all. Easy to see by moving the window by dragging it with the title bar after you first clicked it. Note how clicking again makes the window jump back but you get neither WM_ERASEBKGND nor WM_PAINT.

That's an optimization at work, making Windows work better. And not mentioned in the MSDN article. If the window didn't move off the screen and back and the size of the window did not change then it can take a shortcut. It simply copies the pixels in the video frame buffer from the old position to the new position. Much more efficient than letting the app repaint everything. If you run with Aero enabled then it is even more optimized, it doesn't have to copy the pixels at all.

Last but not least, while writing code like this to reverse-engineer Windows is pretty educational and recommended, you don't have to. It is much easier to use the Spy++ utility.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Great explanation on the optimization aspect (how Windows can sometimes paint my window without asking it to do so). But this does not explain why the window procedure was not called immediately and passed a `WM_PAINT` message (as stated in the documentation), but rather it was called later as part of the message loop, I mean if Windows want to use optimization in this case, it should have not sent a `WM_PAINT` message in the first place, but since it did anyway, it should have done so by calling `UpdateWindow()`! –  Oct 14 '15 at 08:43
  • Whether it posts or sends the messages is another heavy implementation detail that is not addressed in MSDN docs. Check [this post](http://stackoverflow.com/a/33100217/17034), heed the warning. The much more interesting case for MoveWindow is when to use FALSE. The unusual case. You do so when you move multiple windows, rearranging layout for example. – Hans Passant Oct 14 '15 at 08:54
  • But it is indeed addressed in the MSDN docs: *"If the bRepaint parameter is TRUE, the system sends the WM_PAINT message to the window procedure immediately after moving the window (that is, **the MoveWindow function calls the UpdateWindow function**)"*. –  Oct 14 '15 at 09:03
  • You are not heeding the warning. Well, as you found out the MSDN documentation is wrong, it is in fact posted and not sent. Maybe it was sent in an earlier version of Windows, I don't remember. I recommend you use the "Community addition" feature of that MSDN page to post a correction to the article. – Hans Passant Oct 14 '15 at 09:09
2

What probably happens is that MoveWindow sends WM_ERASEBKGND using SendMessage (which will call the WndProc callback immediately and wait for its processing) but WM_PAINT via PostMessage (which will just put the message in the queue, so it will be processed after sleeping, when DispatchMessage is called).

I don't know if it's just a test or you're really processing something after using MoveWindow which blocks the message queue. If so, then you should consider moving that work to another thread!

Hope it helps.

cdonts
  • 9,304
  • 4
  • 46
  • 72
  • My code is just a test to see how `MoveWindow()` works. The documentation says: *"the MoveWindow function calls the UpdateWindow function"*, now `UpdateWindow()` should call the window procedure directly and pass it a `WM_PAINT` message, but this is not happening, so does that mean that the documentation is wrong, or am I missing something? Also, isn't it wrong to pass `WM_PAINT` to `PostMessage()`, and the correct thing to do is to just invalidate the region? –  Oct 10 '15 at 23:47
  • The quote you mentioned is from the `UpdateWindow()` documentation I assume. Yes that is true what you said, if I do not invalidate a region before calling `UpdateWindow()`, no message will be sent. But when I called `MoveWindow()` in my code, it increased the size of the window, and so a portion of the window is invalidated. So now when `MoveWindow()` calls `UpdateWindow()` (as mentioned in the `MoveWindow()` documentation), the window procedure should be called directly and a `WM_PAINT` message should be passed to it, but this is not happening! –  Oct 11 '15 at 02:11
  • @rony_t That's right. I've tested `UpdateWindow` with a previous `InvalidateRect` and `WM_PAINT` is sent using `SendMessage`. So probably the documentation is wrong when saying that `MoveWindow` calls `UpdateWindow` (I've disassembled it and didn't see any call). – cdonts Oct 12 '15 at 17:30
2

Did I interpret the documentation wrong?

Yes and no.

No - the documentation is pretty clear on this.

Yes - like Hans Passant said, you just can't rely on such details from MSDN.

There are many WinAPI functions that have "gotchas", undocumented behaviour or expected environment state (incl. timing) and such. It could be that it did behave as specified in some version of Windows. Maybe it still does, under some circumstances.

In practice, MS will test a lot of applications to see if they work after making a change like this. In this case, since you generally process the WM_PAINT "when it happens", and do only painting then, it is easy to see how in most applications this change would not affect the end-user result.

So, always take MSDN as a "general description". Use your own testing and other sources to get the actual behaviour details. Be happy that you're working with the windows-related API, if you ever work some less used APIs, you'll be far worse off (I suffered a lot with the USB / HID related APIs).

srdjan.veljkovic
  • 2,468
  • 16
  • 24
1

i didn't want to override rony's provided test code so i decided to post my alternative, which i think is better suited to debug Windows Message, the only requirement an external tools to view the debug message DebugView. this of course can be replaced by a listbox if some one wishes.

#include <Windows.h>

/*
   get DebugView from here https://download.sysinternals.com/files/DebugView.zip
*/



/*___________________________________________________________________________________
*/
void __cdecl  DebugPrint(TCHAR *fmt,...){
    va_list args = NULL;
    va_start(args, fmt);
    static TCHAR _buff[512]="";
    TCHAR buff[512];
    wvsprintf(buff,fmt,args);
    va_end(args);
    if(lstrcmp(buff,_buff))
        OutputDebugString(buff);
    lstrcpy(_buff,buff);
    return ;

}

/*___________________________________________________________________________________
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_RBUTTONDOWN:
            MoveWindow(hWnd, 200, 200, 700, 700, TRUE);
            break;
        case WM_MOVE:
            DebugPrint("WM_MOVE");
            break;    
        case WM_SIZE:
        DebugPrint("WM_SIZE");
       break;

    case WM_ERASEBKGND:
        DebugPrint("WM_ERASEBKGND");
        break;
    case WM_PAINT:
        DebugPrint("WM_PAINT");

        if(1){
            PAINTSTRUCT ps;
            TCHAR buff[]="Right mouse click on the window to call MoveWindow()";
            HFONT hf=(HFONT)GetStockObject(DEFAULT_GUI_FONT);
            HDC hdc = BeginPaint(hWnd, &ps);
                hf=SelectObject(hdc,hf);
                TextOut(hdc,8,12,buff,  sizeof(buff)-sizeof(TCHAR));
                hf=SelectObject(hdc,hf);
            EndPaint(hWnd, &ps);
        }else{
            return DefWindowProc(hWnd, message, wParam, lParam);
            }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);

    }

    return 0;
}
/*___________________________________________________________________________________
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    ZeroMemory(&wc,sizeof(wc));

    wc.cbSize = sizeof(WNDCLASSEX);

    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
    wc.lpszClassName = "WinClass";
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    RegisterClassEx(&wc);

    HWND hWnd = CreateWindowEx(0, "WinClass", "",WS_OVERLAPPEDWINDOW|WS_VISIBLE, 
        261, 172, 594, 384, NULL, NULL, hInstance, NULL);


    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
milevyo
  • 2,165
  • 1
  • 13
  • 18