【问题标题】:Erasing Window Background擦除窗口背景
【发布时间】:2018-06-06 13:22:28
【问题描述】:

据我了解,Windows 对给定窗口的(重新)绘制进行了分工;分为背景擦除和前景绘画。发送WM_ERASEBKGNDmessage 是为了准备给定窗口的无效部分进行绘画,通常这种准备包括擦除背景,以便可以在干净的画布上开始实际绘画。在我看来,这条消息总是在 Windows 使给定窗口的一部分无效时发送(因此基本上总是与正在发布的 WM_PAINT 消息一起发送)。每当应用程序本身使给定窗口(部分)无效时,InvalidateRect 函数的最后一个参数指定是否发送WM_ERASEBKGND。所以我写了一个小程序来测试我的假设,但它的行为让我有点难以理解。这是说程序:

#ifndef UNICODE
#define UNICODE
#endif 

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hwInstance, PWSTR pCmdLine, int nCmdShow)
{
    // Register the window class.
    const wchar_t CLASS_NAME[] = L"Sample Window Class";

    WNDCLASS wc = {0};

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    // Create the window.

    HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        L"Learn to Program Windows",    // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

                                        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
    );

    if (hwnd == NULL)
    {
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Run the message loop.

    MSG msg = {0};
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static int eb_count = 0; // counts number of WM_ERASEBKGND messages

    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        RECT rect;

        wchar_t text[40]; 
        wsprintf(text, L"Number of WM_ERASEBKGND messages: %i\n", eb_count);

        GetClientRect(hwnd, &rect);
        DrawText(hdc, text, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

        EndPaint(hwnd, &ps);
        return 0;

    case WM_RBUTTONDOWN: // repaint whenever RBUTTONDOWN
        InvalidateRect(hwnd, NULL, FALSE);
        UpdateWindow(hwnd);
        return 0;

    case WM_ERASEBKGND:
        eb_count++;
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

我在窗口过程中处理WM_ERASEBKGND(这是我的开关中的一个案例),所以它不应该传递给默认窗口过程。但是,我没有进行任何实际的背景擦除(我只是增加一个静态变量)并且我返回 0 表示实际上没有发生擦除。在我看来,在这个程序中,背景永远不应该被抹去。然而,这确实发生在两个不同的情况下。

每当我最大化窗口时,无效部分的背景会被类背景画笔擦除。但这怎么可能?窗口过程在收到WM_ERASEBKGND 消息时肯定不会做这样的事情。

每当DrawText 重新绘制其字符串时,都会发生类似的事情。我希望递增的数字会被绘制在彼此之上(当然会导致难以辨认的混乱)。因此,“DrawText”函数似乎也以某种方式擦除了它绘制字符串的矩形的背景。

我的最后一个问题与我的假设有关,即只要 Windows 使窗口的一部分无效,就会发送 WM_ERASEBKGND 消息。我注意到,每当窗口被另一个窗口覆盖并随后被覆盖时,似乎都没有发送WM_ERASEBKGND 消息。这是否意味着我的假设是错误的? 很抱歉读了这么久,但在回答这些问题方面的任何和所有帮助将不胜感激。

【问题讨论】:

  • 查看 WM_ERASEBKGND 的文档 - 您没有擦除背景,并且您返回 0 以响应 WM_ERASEBKGND。也许你想要case WM_ERASEBKGND:break; 或者干脆不说。或者在WM_PAINT做背景画
  • 如果你想阻止默认背景绘制,在处理WM_ERASEBKGND的代码中什么都不做,并返回一个非零值告诉Windows你已经处理了它。在这种情况下,您应该在WM_PAINT 中完成所有绘画(包括背景绘画)。为防止DrawText()清除后台,请先调用SetBkMode(TRANSPARENT)
  • @zett42 返回 0 还是非零值真的很重要吗?据我所知,唯一真正的区别是,当我返回 0 时,PAINTSTRUCTfErase 标志将设置为 TRUE。如果我返回一个非零值,该标志将设置为FALSE。除此之外,没有真正的区别。在任何一种情况下,真正重要的是在WM_ERASEBKGND 期间实际发生的事情,这在我的程序中什么都不是。所以无论我返回什么,无论哪种情况我都可以处理WM_PAINT中的所有绘画,对吧?
  • reference 并不是说​​PAINTSTRUCT::fErase 标志是唯一受WM_ERASEBKGND 的返回值影响的东西。注意“通常”这个词。所以我会安全地玩它并且总是返回TRUE

标签: winapi background erase


【解决方案1】:

...我假设每当 Windows 使窗口的一部分无效时都会发送 WM_ERASEBKGND 消息。我注意到每当窗口被另一个窗口覆盖并随后被覆盖时,似乎都没有发送 WM_ERASEBKGND 消息......

那是因为,在 Vista 和更高版本中,Desktop Window Manager (DWM) 潜伏在背景中。这会缓冲所有屏幕窗口的内容,这样当窗口的一部分被发现时,Windows 不需要发出 WM_ERASEBKGND 或 WM_PAINT 请求 - 它只需将所谓的后台缓冲区复制回屏幕即可。

[Parts of] 窗口仍然会失效——无论是你还是操作系统——但不像 XP 时代那样频繁。例如,尝试最小化和恢复一个窗口 - 然后必须重新绘制它。当您这样做时,DWM 可能会在窗口最小化时丢弃后台缓冲区以节省内存。

除此之外,其他人在 cmets 中所说的话。

【讨论】:

  • 感谢您的回答,这解释了它。你能解释一下为什么当我最大化窗口时,窗口无效部分的背景仍然会被类背景画笔擦除吗?
  • 从 WM_ERASEBKGND 返回 TRUE 而不是 0(如前所述 - 两次)。或者将类背景画笔设置为 GetStockObject (NULL_BRUSH)。
  • 不需要被动攻击性响应。我的问题不关心如何,而是关心为什么。通过简单地不设置类背景画笔,我可以轻松获得与NULL_BRUSH 相同的结果,但这根本不是我问题的本质。此外,无论我返回TRUE 还是FALSE 都没有关系,在这两种情况下都会发生相同的行为。因此,我之前的评论认为,似乎唯一受返回值影响的是PAINTSTRUCTfErase 字段。
  • 对不起,我已经重新阅读了上面的cmets,我欠你一个道歉。看起来 WM_ERASEBKGND 的行为确实不像记录的那样。你看到下面zett42的链接了吗?我认为这是关键。现在我停下来想一想,不久前我和你有过类似的经历,而且我认为,这些天来,Windows 可能会使用背景画笔自行绘制窗口,原因在链接到的文章中解释了原因.我的记忆很模糊,但有一段时间我窗户的外露边缘被涂成黑色(直到我的绘图代码赶上),直到我整理好我的画笔。
  • 投了你一票,以弥补我的粗鲁(因为我喜欢这个问题)。
【解决方案2】:

根据@Paul Sanders 最近的回答,桌面窗口管理器实际上是一个缓存窗口内容的进程,因此它可以在组合桌面时执行混合效果,这意味着您的窗口并不总是像在早期版本的 Windows 中那样重新绘制。

在此之前,从协同多任务到多线程操作系统的转变(这种绘画模型在 Windows 3.0 API 中)引入了一些竞争条件,iirc,Windows 会尝试通过先发制人来掩盖在某些情况下,当某个进程更改了另一个进程窗口的可见性时,背景画笔填充。这是您最大化窗口时看到的内容。

您对DrawText 的调用是有效的,因为DrawText - 默认情况下 - 会自行擦除背景 - 您需要调用SetBkMode 并传入TRANSPARENT 标志以仅呈现字体。

【讨论】:

猜你喜欢
  • 2011-07-09
  • 2015-05-08
  • 1970-01-01
  • 2015-11-13
  • 2020-09-02
  • 2010-10-01
  • 2016-08-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多