Strange coordinates in `WM_MOUSEMOVE` - c

MSDN describes that lParam of WM_MOUSEMOVE is 2 shorts due to it needing to be compatible with virtual coordinates because it acts as a redirected event if capture is set, which is clear. However, negative coordinates are still received under normal circumstances when moving the mouse slightly outside of the window, to be exact a bonus 5 pixels in all directions except up (which is where the caption is, and it'd go to WM_NCMOUSEMOVE instead).
I initially suspected this was to do with the drop shadow technically being part of the window (like with the output of AdjustWindowRectEx's rectangle including it) since you can receive events in some of the shadow, but the values of that don't match up, and clamping the values to the client area's size doesn't feel intended. Where do the bonus 5 pixels (on my system, Windows 10 Education 2004) come from, especially considering that shouldn't even be a part of the client area to my knowledge, and is there a clean/intended way to dodge unwanted values?
I've seen some discussion about the area you can grab to resize the window potentially being related, but my window isn't resizable (doesn't have a thick frame).
Edit: After tinkering, it seems this is related to the window style. Here's a reproducible sample (hopefully on other machines, too):
#include <Windows.h>
#include <stdio.h>
// Remove `^ WS_THICKFRAME` and the bug vanishes!
#define WINDOW_STYLE ((WS_OVERLAPPEDWINDOW | WS_VISIBLE) ^ WS_THICKFRAME)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_MOUSEMOVE) {
POINTS mouse = MAKEPOINTS(lParam);
printf("WM_MOUSEMOVE # x=%hd, y=%hd\n", mouse.x, mouse.y);
} else if (message == WM_DESTROY) {
PostQuitMessage(0);
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
int main(void) {
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = GetModuleHandle(NULL);
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"SO_Example";
wcex.hIconSm = NULL;
RECT windowRect = { 0, 0, 600, 600 };
AdjustWindowRectEx(&windowRect, WINDOW_STYLE, FALSE, 0);
ATOM windowClass = RegisterClassExW(&wcex);
HWND hWnd = CreateWindowExW(
0,
(LPCWSTR)windowClass,
L"hello stackoverflow!",
WINDOW_STYLE,
CW_USEDEFAULT, CW_USEDEFAULT,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0) != 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
Here's that sample in action: https://i.imgur.com/LfCe9od.mp4

It appears 5 is the border size (1) and the border padding (4) added up (aka the area the user can use to resize the window), so it's safe to discard values outside of the client rectangle, even if it's a little strange that Win32 is reporting events in that area.

Related

C: Fastest way to determine, if any key on the keyboard has been pressed

I am searching for the fastest possible way in C to determine if any key on the keyboard has been pressed.
I am not looking for how to determine if a specific key is pressed (in that case, GetAsyncKeyState() would work).
Also, it needs to work in the background, so without the program window having focus (the program will be running in the background).
EDIT: The program will react on every keypress with a sound output. I want it to output a sound everytime I type something (like in Word and such). That's also why it needs to run in the background. I want it to be fast, so I can minimize the delay between keypress and sound output.
EDIT2: I am searching for something else than Windows Hooks. While it does work for getting key presses in the background, I am looking for something that is faster (least delay possible).
For example: GetAsyncKeyState() works for reacting on specific keypresses, while the program window doesn't have focus. I am looking for something like that, but with the ability to react on any key press, not a specific one.
As comment, you could use RegisterRawInputDevices as this sample.
Create a Message-Only Window.
Set RAWINPUTDEVICE.hwndTarget to the window create in step 1, so that you don't need to focus on the window.
Call GetRawInputData to get the input data.
Sample(removed the error checking):
#include <windows.h>
#include <iostream>
using namespace std;
LRESULT CALLBACK WindProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == WM_INPUT)
{
HRAWINPUT hRawInput = (HRAWINPUT)lParam;
RAWINPUT input = { 0 };
UINT size = sizeof(input);
GetRawInputData(hRawInput, RID_INPUT,&input,&size,sizeof(RAWINPUTHEADER));
printf("vkey: %x, flag: %d\n",input.data.keyboard.VKey, input.data.keyboard.Flags);
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
int main()
{
WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.lpfnWndProc = WindProc;
wcx.hInstance = GetModuleHandle(NULL);
wcx.lpszClassName = TEXT("RawInputClass");
RegisterClassEx(&wcx);
HWND hWnd = CreateWindowEx(0, TEXT("RawInputClass"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
RAWINPUTDEVICE rid = { 0 };
rid.usUsagePage = 0x01;
rid.usUsage = 0x06; //keyboard
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = hWnd;
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}

Magnification API: flickering during repaint

I created an application by slightly modifying the Magnification API sample provided by Microsoft to implement a custom transformation of each frame captured and displayed in the Magnifier window.
I used the MagSetImageScalingCallback to set the my function as a callback. The callback is invoked without problem and the source and destination image can be easily manipulated because the raw bits are passed to the callback as pointers (srcdata and destdata).
The window is refreshed with a timer which is set to 16 ms (~60Hz). The refresh is invoked by using the InvalidateRect API. The problem is that when the magnifier window suffer from flickering. This happens especially when the start menu appears, if "Peek" is enabled or every time there is a window on foreground which has dynamic content.
I tried to intercept the WM_ERASEBKGND and invoke the InvalidateRect with FALSE as third parameter but this didn't help. I tried to add an UpdateWindow invocation before the invalidate but nothing changed.
The Magnifier application provided with Windows 10 doesn't have the same problem. I'm wondering why this is happening and how can I get rid of flickering.
In order to reproduce che problem download the Magnification API sample from the link above then replace the content in the file MagnifierSample.cpp with this source code:
// Ensure that the following definition is in effect before winuser.h is included.
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <wincodec.h>
#include <magnification.h>
// For simplicity, the sample uses a constant magnification factor.
#define RESTOREDWINDOWSTYLES WS_POPUP
// Global variables and strings.
HINSTANCE hInst;
const TCHAR WindowClassName[]= TEXT("MagnifierWindow");
const TCHAR WindowTitle[]= TEXT("Screen Magnifier Sample");
const UINT timerInterval = 16; // close to the refresh rate #60hz
HWND hwndMag;
HWND hwndHost;
RECT magWindowRect;
RECT hostWindowRect;
// Forward declarations.
ATOM RegisterHostWindowClass(HINSTANCE hInstance);
BOOL SetupMagnifier(HINSTANCE hinst);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK UpdateMagWindow(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
BOOL isFullScreen = FALSE;
//
// FUNCTION: WinMain()
//
// PURPOSE: Entry point for the application.
//
int APIENTRY WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE /*hPrevInstance*/,
_In_ LPSTR /*lpCmdLine*/,
_In_ int nCmdShow)
{
if (FALSE == MagInitialize())
{
return 0;
}
if (FALSE == SetupMagnifier(hInstance))
{
return 0;
}
ShowWindow(hwndHost, nCmdShow);
UpdateWindow(hwndHost);
// Create a timer to update the control.
UINT_PTR timerId = SetTimer(hwndHost, 0, timerInterval, UpdateMagWindow);
// Main message loop.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Shut down.
KillTimer(NULL, timerId);
MagUninitialize();
return (int) msg.wParam;
}
//
// FUNCTION: HostWndProc()
//
// PURPOSE: Window procedure for the window that hosts the magnifier control.
//
LRESULT CALLBACK HostWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
return TRUE;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//
// FUNCTION: RegisterHostWindowClass()
//
// PURPOSE: Registers the window class for the window that contains the magnification control.
//
ATOM RegisterHostWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = HostWndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wcex.lpszClassName = WindowClassName;
return RegisterClassEx(&wcex);
}
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty) {
UINT row;
UINT column;
BYTE *matrix;
memset(destdata, 0, destheader.cbSize);
matrix = (BYTE *) destdata;
//Set alpha bits for the resulting image to 255 (fully opaque)
for (row = 0; row < destheader.height; row++) {
for (column = 3; column < destheader.width; column += 4) {
matrix[(row * destheader.width) + column] = 0xFF;
}
}
Sleep(20);
return TRUE;
}
//
// FUNCTION: SetupMagnifier
//
// PURPOSE: Creates the windows and initializes magnification.
//
BOOL SetupMagnifier(HINSTANCE hinst)
{
// Set bounds of host window according to screen size.
hostWindowRect.top = 0;
hostWindowRect.bottom = GetSystemMetrics(SM_CYSCREEN);
hostWindowRect.left = 0;
hostWindowRect.right = GetSystemMetrics(SM_CXSCREEN);
// Create the host window.
RegisterHostWindowClass(hinst);
hwndHost = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED, WindowClassName, WindowTitle, RESTOREDWINDOWSTYLES, 0, 0, hostWindowRect.right, hostWindowRect.bottom, NULL, NULL, hInst, NULL);
if (!hwndHost)
{
return FALSE;
}
// Make the window opaque.
SetLayeredWindowAttributes(hwndHost, 0, 255, LWA_ALPHA);
SetWindowLong(hwndHost, GWL_EXSTYLE, GetWindowLong(hwndHost, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE);
// Create a magnifier control that fills the client area.
magWindowRect = hostWindowRect;
hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"), WS_CHILD | WS_VISIBLE, magWindowRect.left, magWindowRect.top, magWindowRect.right, magWindowRect.bottom, hwndHost, NULL, hInst, NULL );
if (!hwndMag)
{
return FALSE;
}
MagSetImageScalingCallback(hwndMag, &myCallBack);
// Set the source rectangle for the magnifier control.
MagSetWindowSource(hwndMag, magWindowRect);
return 1;
}
//
// FUNCTION: UpdateMagWindow()
//
// PURPOSE: Sets the source rectangle and updates the window. Called by a timer.
//
void CALLBACK UpdateMagWindow(HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR /*idEvent*/, DWORD /*dwTime*/)
{
// Reclaim topmost status, to prevent unmagnified menus from remaining in view.
SetWindowPos(hwndHost, HWND_TOPMOST, 0, 0, magWindowRect.right, magWindowRect.bottom, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
// Force redraw.
InvalidateRect(hwndMag, NULL, FALSE);
}
Notice that I added the code to set the callback
BOOL ret = MagSetImageScalingCallback(hwndMag, &myCallBack);
then I created this callback:
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
The Sleep statement "simulate" the time taken by my custom transformation.
If you open a youtube video with Edge and then you run the MagnifierSample.exe executable you should see a black screen flickering sometime and when the screen flickers you should see the content behind the magnifier window (the black screen). This is exactly what is happening in my application. The sleep value is set to 20 but I don't known how long the callback is actually taking. I just guessed that it might take more than 16 ms.
To reproduce the problem perform the following steps:
Run the MagnifierSample.exe manually (don't use the debugger to run it)
You should see a black screen
Press the window key
Set a window with a video or a dynamic content in foreground
Click on the black window to let the taskbar disappear
You should see the screen flashing sometime
Is it possible to set a callback similar to the MagSetImageScalingCallback for a normal window handle ? I know I can use the WindowProc callback to intercept the messages sent to the window however I don't have access to the rawbits unless I use CreateDIBSection, SelectObject etc ...but at that time the image has been already sent to the destination window and I don't have the chance to transform it.

Why am I not getting WM_UPDATEUISTATE?

I have a main window with an edit control and a custom button. When the edit control has focus and I press the Tab key, the button control gets the focus but it never receives WM_UPDATEUISTATE? I'm using IsDialogMessage() in the message loop of main window. Any ideas on why this is happening?
Edit: Why isn't the custom button control receiving WM_UPDATEUISTATE?
Edit: IsDialogMessage() is not sending WM_UPDATEUISTATE or WM_CHANGUISTATE in this example
#include <windows.h>
#include <tchar.h>
HINSTANCE g_hInst;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static HWND hBtn1, hBtn2;
switch(msg)
{
case WM_CREATE:
hBtn1 = CreateWindowEx(0, TEXT("Button"), TEXT("Button 1"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
4, 4, 100, 40, hwnd, 0, g_hInst, 0);
if(!hBtn1) return -1;
hBtn2 = CreateWindowEx(0, TEXT("Button"), TEXT("Button 2"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
114, 4, 100, 40, hwnd, 0, g_hInst, 0);
if(!hBtn1) return -1;
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
const TCHAR szClassName[] = TEXT("Main////");
WNDCLASSEX wc;
HWND hwnd;
MSG msg;
SecureZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);;
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = szClassName;
if(!RegisterClassEx(&wc)) return 0;
g_hInst = hInstance;
hwnd = CreateWindowEx(0, szClassName, TEXT("Main"), WS_OVERLAPPEDWINDOW, 140, 140, 440, 240, 0, 0, hInstance, 0);
if(!hwnd) return 0;
ShowWindow(hwnd, nCmdShow);
while(GetMessage(&msg, 0, 0, 0) > 0)
{
if(!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
I've used spy++ for the main window and the 2 buttons when pressing the Tab key and I don't see neither WM_UPDATEUISTATE nor WM_CHANGEUISTATE anywhere, but the standard button control draws the focus rect. How does the standard button know when to draw the focus rect?
A window should show its focus indicator when it has focus. For that, a control should watch for WM_SETFOCUS and WM_KILLFOCUS. When it has the focus, a well-behaved control will check the UI state by sending WM_QUERYUISTATE to itself, and, if the UI state has the UISF_HIDEFOCUS flag set, the control would not draw the focus indicator.
The WM_CHANGEUISTATE and WM_UPDATEUISTATE are responsible for controlling whether a tree of windows (including controls) should display keyboard "cues" and focus indicators in general. For example, if the cues aren't showing and you tap the ALT key, these messages will update the UI state of the windows in the tree so that they start showing the cues. These messages are not used for hiding the focus indicator from one control and showing it on the next as the user TABs through the controls. That effect happens because the controls pay attention to the focus change messages.
Generally speaking, a custom control should watch for the focus change messages and let DefWindowProc handle the UI state messages.
Raymond Chen has a series of blog posts about the UI state and how these messages propagate.
Untangling the confusingly-named WM_UPDATEUISTATE and WM_CHANGEUISTATE messages
Who sends the initial WM_UPDATEUISTATE message?
Why does tapping the Alt key cause my owner-draw static control to repaint?
What is the documentation for SetParent trying to tell me about synchronizing the UI state?
Demonstrating what happens when a parent and child window have different UI states
Getting a parent and child window to have the same UI states
I found these helpful when I recently tried to work all this out again. The current state of the official documentation on this topic is a bit vague and ambiguous in places.
Note: The terms "mnemonics," "accelerators," and "hot keys" have blurred together, even in the official Microsoft documentation and some of the API names. When I say "keyboard cues," I'm mostly referring to mnemonics (sometimes called "shortcuts" or "access keys"). Those are the underlined characters in control labels and menu items that let the user know that ALT+<character> will select the labeled item.

How to add tooltip to menu item

I'm looking for a solution to display a tooltip for a single menu item in my WinAPI program.
ID_MAIN_MENU MENU
BEGIN
...
END
MENUITEM "?", ID_RIGHT_BUTTON, HELP
END
I'm able to add tooltip for a control with TOOLINFO structure and TTM_ADDTOOL message, but this seems not apply to HMENU handle.
I found some C++ libraries that add tooltips to menu items, but I'm using C.
Moreover here it's not possible to use the WM_MENUSELECT message, which is sent only clicking on the menu, not hovering over.
An hypothetical image of what I want.
Any suggestion is appreciated.
Finally come to a solution good enough to make the tooltip appear on mouse over and disappear on mouse out (not perfect because has no initial nor autopop delay time).
#define _WIN32_IE 0x0300 // to reach TTM_TRACKACTIVATE in <commctrl.h>
#include <windows.h>
#include <commctrl.h>
HWND hToolTip;
TOOLINFO ti = {0};
// WndProc
case WM_CREATE: {
HMENU hmenu = CreateMenu();
AppendMenu( hmenu, MF_HELP, 123, "?" );
SetMenu( hWnd, hmenu );
hToolTip = CreateWindow( TOOLTIPS_CLASS, 0, WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, hWnd, 0, 0, 0);
ti.cbSize = sizeof(TOOLINFO);
ti.hwnd = hWnd;
ti.uId = (UINT)hToolTip;
ti.lpszText = "My tooltip";
SendMessage( hToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti );
break;
}
case WM_NCMOUSEMOVE: {
RECT rect;
GetMenuItemRect( hWnd, GetMenu(hWnd), 0, &rect );
// ^ zero-based position of the menu item
int x = LOWORD(lParam);
int y = HIWORD(lParam);
if ( x>=rect.left && y>=rect.top && x<rect.right && y<rect.bottom )
SendMessage( hToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti );
else
SendMessage( hToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti );
break;
}
case WM_MOUSEMOVE:
SendMessage( hToolTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti );
break;
Why I did use TTM_TRACKACTIVATE instead of TTM_POPUP?
Oddly TTM_POPUP is not defined in commctrl.h by MinGW (gcc 5.3.0).
TTM_POPUP needs Visual Styles enabled with manifest.
Eventually for me TTM_POPUP is ineffective in this scenario.
I distilled a simpler and more efficient solution to create a tooltip for a single menu item:
#include <windows.h>
#include <commctrl.h>
#define ID_BUTTON 123
HWND hToolTip;
TOOLINFO ti = {0};
// WndProc
case WM_CREATE: {
// create menu
HMENU hmenu = CreateMenu();
AppendMenu( hmenu, MF_HELP, ID_BUTTON, "?" );
SetMenu( hWnd, hmenu );
InitCommonControls(); // necessary in Windows XP to register TOOLTIPS_CLASS
// create tooltip
hToolTip = CreateWindow( TOOLTIPS_CLASS, 0, WS_POPUP, 0, 0, 0, 0, hWnd, 0, 0, 0 );
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_SUBCLASS;
ti.hwnd = hWnd; // main window handle
ti.uId = (UINT)ID_BUTTON; // distinguish it from eventual other tooltips
ti.lpszText = "My tooltip";
SendMessage(hToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
break;
}
case WM_SIZE:
ti.uId = (UINT)ID_BUTTON;
// get new coordinates of item
GetMenuItemRect( hWnd, GetMenu(hWnd), 0, &ti.rect );
// ^ zero-based position of the menu item
// convert from screen to client coordinates
MapWindowPoints( NULL, hWnd, (LPPOINT)&ti.rect, 2 );
// ^ desktop handle ^ POINT structures that is one RECT
SendMessage(hToolTip, TTM_NEWTOOLRECT, 0, (LPARAM)&ti);
break;
I used InitCommonControls because is shorter and works, but is also obsolete and InitCommonControlsEx should be used instead.
Above code was tested also on Windows XP: it works, but GetLastError after CreateWindow returns a 1309 ERROR_NO_IMPERSONATION_TOKEN.
In order to solve it, create a manifest file and in resource (.rc) file add the line:
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"

Calling MoveWindow() with bRepaint set to TRUE

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

Resources