擦除窗口背景



据我所知,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: %in", 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消息。这是否意味着我的假设是错误的? 很抱歉阅读时间很长,但回答这些问题的任何和所有帮助将不胜感激。

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

这是因为,从Vista及更高版本开始,桌面窗口管理器(DWM(潜伏在后台。 这会缓冲所有屏幕窗口的内容,以便Windows在发现一个窗口的一部分时不需要发出WM_ERASEBKGND或WM_PAINT请求 - 它可以将所谓的后台缓冲区复制回屏幕。

[部分]窗口仍然会失效 - 无论是由您还是操作系统 - 但不像XP时代那样频繁。 例如,尝试最小化和恢复窗口 - 然后必须重新绘制它。 执行此操作时,DWM 可能会丢弃后台缓冲区以节省内存,同时最小化窗口。

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

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

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

您对DrawText的调用正在工作,因为默认情况下DrawText会擦除自己的背景 - 您需要调用SetBkMode传入TRANSPARENT标志以仅呈现字体。

最新更新