由于无效的 ValidateRgn() 孩子 windows 没有收到 WM_PAINT 消息

Due to invalid ValidateRgn() childs windows do not get WM_PAINT messages

我有window和Button控制,画图我用Direct2D,重要的WindowProc片段:

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

这段代码不太行,我的childButton没有画出来。我想它没有收到 WM_PAINT 消息,因为我验证了整个 window,因此 Window 不要要求重新绘制相同的像素两次。

所以我考虑到了这一点,并从验证中排除 Button 区域:

    // 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

现在Button还没有画,而且我在无限循环中收到WM_PAINT,更新区域等于Rgn(10, 10, 210, 110)。我预计在 WindowProc 中的 WM_PAINT 之后,child Button 应该会收到它,并且将验证缺少的更新区域。


如果我将 WM_PAINT 消息包装在 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;

似乎 BeginPaint() 验证更新区域并为 children windows 触发 WM_PAINT。即使您不使用 GDI 而它是 PAINTSTRUCT->HDC,也应该调用它来响应 WM_PAINT。 但是如果 BeginPaint() 确实为 children windows 触发了 WM_PAINT 那么我的日志调用顺序应该看起来像 WINDOW_PAINT_START -> BUTTON_PAINT_START -> BUTTON_PAINT_END -> WINDOW_PAINT_END,实际上看起来像 WINDOW_PAINT_START -> WINDOW_PAINT_END -> BUTTON_PAINT_START -> BUTTON_PAINT_END。因此 ButtonWM_PAINTWM_PAINT 延迟到 WinProc returns:因此触发发生在外部,因此 children 绘图发生在 main [ 之后=65=] 符合MSDN的paint。

那么 WM_PAINT 到 children windows 究竟是在哪个时刻派遣的?

MSDN 帮忙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.

关于无限循环之谜:在代码中,您仅 validate/invalidate(操作)MainWindow 区域,因此在 WindowProc return 之后 -> 队列为空 -> 区域不清楚 -> WM_PAINT 正在发送到 window.

如果要为 child window 生成 WM_MESSAGE,请使 child window 更新区域无效。请记住,原点 (0,0) 已更改为相对于 child 更新区域,它不再是 main(parent) window 更新区域:

    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.

child 的失效区域何时发生?

MSDN 在这件事上可能会产生误导(但也许只是你理解不同),再次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.

但实际上,当您为 main window 调用 InvalidateRgn(hWnd, ..., ...) 时,不会发生 children windows 的无效,而是响应 [=13] =],更准确地说 BeginPaint(hWnd, ...) 在内部对区域进行一些计算并为 child 调度失效(但我不确定,这只是我的观察)。