I have window with Button
control, to drawing I use Direct2D
, important WindowProc
fragments:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
...
case WM_CREATE:
HWND button = CreateWindowExW(0, L"button", L"Send", WS_CHILD | WS_VISIBLE, 10, 10, 200, 100, hWnd, CONTROL_ID, desc->hInstance, 0);
break;
case WM_PAINT:
render_target->BeginDraw();
... rendering stuff ...
HRESULT result = render_target->EndDraw();
// Validate region:
ValidateRgn(hWnd, nullptr); // validate entire client area
break;
...
}
This code doesn't quite work, my child Button
was not painted. I suppose it doesn't receive WM_PAINT
message because I validated entire window, hence Window don't ask to repaint the same pixels twice.
So I take that into consideration, and exclude Button
region from validation:
// Validate region:
HRGN update = CreateRectRgn(0, 0, 0, 0);
HRGN button = CreateRectRgn(10, 10, 210, 110);
GetUpdateRgn(hWnd, update, false); // get update rect
CombineRgn(update, update, tab, RGN_DIFF); // exclude button region
ValidateRgn(hWnd, update); // validate window client area (excluding button) to stop receiving WM_MESSAGE
Now Button
still is not painted, what's more I receive WM_PAINT
in infinite loop, with update region equal Rgn(10, 10, 210, 110)
. I expected that after WM_PAINT
in WindowProc
, child Button
should received it and there missing update region will be validated.
Everything works if I wrap WM_PAINT
message in BeginPaint(hWnd, nullptr)
EndPaint(hWnd, nullptr)
:
case WM_PAINT:
::log << "WINDOW_PAINT_START" << std::endl;
BeginPaint(hWnd, nullptr); // use BeginPaint from GDI
render_target->BeginDraw();
... rendering stuff ...
HRESULT result = render_target->EndDraw();
// Validate region code is not needed anymore because BeginPaint take care of that.
EndPaint(hWnd, nullptr);
::log << "WINDOW_PAINT_END" << std::endl;
break;
It seems to BeginPaint()
validate update region and trigger WM_PAINT
for children windows. It should be called in response to WM_PAINT
even if you don't use GDI
and it's PAINTSTRUCT->HDC
.
But if BeginPaint()
indeed trigger WM_PAINT
for children windows then my log calling order should looks like WINDOW_PAINT_START -> BUTTON_PAINT_START -> BUTTON_PAINT_END -> WINDOW_PAINT_END
, in fact looks like WINDOW_PAINT_START -> WINDOW_PAINT_END -> BUTTON_PAINT_START -> BUTTON_PAINT_END
. So WM_PAINT
for Button
is delayed until WinProc
returns from it's WM_PAINT
: hence trigger happen outside, hence children drawing happend after main window paint which is in compliance with MSDN.
So exactly in which moment WM_PAINT
to children windows is dispatched?
MSDN come in some help Child Window Update Region:
Each child window has an update region, (...) The system does not set the parent's update region when the child's is set. An application cannot generate a WM_PAINT message for the parent window by invalidating the child window. Similarly, an application cannot generate a WM_PAINT message for the child by invalidating a portion of the parent's client area that lies entirely under the child window. In such cases, neither window receives a WM_PAINT message.
About infinite loop mystery: in code you validate/invalidate (manipulate) region for MainWindow
only, hence after WindowProc
return -> queue is empty -> region isn't clear -> WM_PAINT
is sending to window.
If you want generate WM_MESSAGE
for child window, invalidate child window update region. Remember that origin point (0,0) was changed to relative to child update region, it's no longer main(parent) window update region:
HRGN wrong = CreateRectRgn(10, 10, 210, 110); // wrong, will produce GUI artifacts, try and see for yourself (only part if any of button will be painted).
HRGN good = CreateRectRgn(0, 0, 200, 100); // good, basically it always be Rgn(0,0 width,height) values in region.
InvalidateRgn(>>>button<<<, good, false); // invalide child window, first param point to button HWND.
When this invalidation region for child happen?
MSDN in this matter may be misleading(but maybe just you understand it differently), again Child Window Update Region:
A child window's update and visible regions are affected by the child's parent window; this is not true for windows of other styles. The system often sets the child window's update region when it sets the parent window's update region, causing the child window to receive WM_PAINT messages when the parent window receives them.
But in reality, this invalidation for children windows doesn't happen when you call InvalidateRgn(hWnd, ..., ...)
for main window, but in response to WM_PAINT
, more precisely BeginPaint(hWnd, ...)
internally do some calculcation on regions and dispatchs invalidation for childs (but I not sure this, it's just my observations).