擦除窗口背景

时间:2018-06-06 13:22:28

标签: winapi background erase

据我了解,Windows在给定窗口的(重新)绘制方面进行了分工;分为背景擦除和前景绘画。发送WM_ERASEBKGND消息以准备给定窗口的无效部分进行绘画,并且通常这种准备包括擦除背景以使实际绘画可以从干净的画布开始。在我看来,当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消息。这是否意味着我的假设是错误的? 很抱歉长时间阅读,但回答这些问题的任何和所有帮助将不胜感激。

2 个答案:

答案 0 :(得分:2)

  

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

那是因为,从Vista和之后的版本开始,我们在{erm}背景中潜伏着Desktop Window Manager (DWM)。这会缓冲所有屏幕窗口的内容,以便Windows在发现部分内容时不需要发出WM_ERASEBKGND或WM_PAINT请求 - 它只需将所谓的后台缓冲区复制回屏幕即可。 / p>

[部分]窗口仍然会被您或操作系统无效 - 但不像以前那样经常在XP时代回来。尝试最小化和恢复窗口 - 然后必须重新绘制。当您这样做时,DWM可能会抛弃后缓冲区以在窗口最小化时节省内存。

除此之外,其他人在评论中说了什么。

答案 1 :(得分:1)

根据@Paul Sanders的回答,最近桌面窗口管理器是一个实际上缓存窗口内容的过程,因此它可以在编写桌面时执行混合效果,这意味着窗口并不总是如此以早期版本的Windows中的方式重新粉刷。

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

您对DrawText的调用正常,因为DrawText - 默认情况下 - 会自行删除背景 - 您需要调用SetBkMode传递TRANSPARENT标记进行渲染只是字体。