WINAPI。不能用椭圆擦除窗口的背景

时间:2017-05-19 09:34:09

标签: c++ winapi

我只想画一个椭圆:

case WM_PAINT:
        hdc = BeginPaint(parentWindow, &ps);
        Ellipse(hdc, x, y, width, height);
        EndPaint(parentWindow, &ps);

,然后使用计时器每秒绘制一个带有一些新参数的新椭圆来擦除它:

case WM_CREATE:
        SetTimer(hWnd, 1, 1000, NULL);
        break;
case WM_TIMER:
        x += 5;
        InvalidateRect(hWnd, NULL, TRUE);
        break;

但椭圆不会被删除和分层:

Ellipses

但是,我试图跟踪WM_ERASEBKGND,它确实是每个InvalidateRect发送的。

完整代码:

#include <Windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <iostream>


TCHAR szWindowClass[] = TEXT("CreateThreadWindow");
TCHAR szAppName[] = TEXT("CreateThreadExample");

BOOL InitWindow(HINSTANCE, int);
ATOM MyRegisterClass(HINSTANCE);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HWND parentWindow;
MSG msg;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MyRegisterClass(hInstance);
    if (!InitWindow(hInstance, nCmdShow))
        return FALSE;
    BOOL bRet;
    while ((bRet = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0)
    {
        if (bRet == -1)
            return FALSE;
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASS wndClass;
    memset(&wndClass, 0, sizeof(wndClass));
    wndClass.lpfnWndProc = WndProc;
    wndClass.hInstance = hInstance;
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = szWindowClass;
    return RegisterClass(&wndClass);
}

BOOL InitWindow(HINSTANCE hInstance, int nCmdShow)
{
    parentWindow = CreateWindow(szWindowClass, szAppName, WS_OVERLAPPEDWINDOW,
        300, 0, 600, 600, NULL, NULL, hInstance, NULL);
    ShowWindow(parentWindow, nCmdShow);
    return TRUE;
}



LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    static int x = 0, y = 0, width = 200, height = 100;
    switch (message) {
    case WM_ERASEBKGND:
        _RPT1(0, "%s\n", "erase");
        break;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        Ellipse(hdc, x, y, width, height);
        EndPaint(hWnd, &ps);
        break;
    case WM_CREATE:
        SetTimer(hWnd, 1, 1000, NULL);
        break;
    case WM_TIMER:
        x += 5;
        InvalidateRect(hWnd, NULL, TRUE);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, message, wparam, lparam);
    }
}

1 个答案:

答案 0 :(得分:5)

您的代码没有删除任何内容。它只是在指定的坐标处绘制一个椭圆。之前绘制的椭圆仍在那里。

您提到了WM_ERASEBKGND消息,但有两个原因导致您无效:

  1. 在窗口过程(WndProc)中,您显式处理WM_ERASEBKGND消息,这意味着它不会传递给默认窗口过程(DefWindowProc) 。编写窗口过程的更好方法如下:

    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
    {
        static int x = 0, y = 0, width = 200, height = 100;
        switch (message) {
        case WM_ERASEBKGND:
        {
            _RPT1(0, "%s\n", "erase");
            break;
        }
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            Ellipse(hdc, x, y, width, height);
            EndPaint(hWnd, &ps);
            return 0;
        }
        case WM_CREATE:
        {
            SetTimer(hWnd, 1, 1000, NULL);
            break;
        }
        case WM_TIMER:
        {
            x += 5;
            InvalidateRect(hWnd, NULL, TRUE);
            return 0;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
        default:
            break;
        }
    
        return DefWindowProc(hWnd, message, wparam, lparam);
    }
    

    现在,除非您从return标签内明确case,否则每次都会调用默认窗口过程。

  2. 当您注册窗口类(MyRegisterClass内)时,将WNDCLASS structure的所有字段归零,然后显式初始化其中的几个字段。您显式初始化hbrBackground字段,因此将其设置为0.当hbrBackground为0时,

      

    当此成员为NULL时,应用程序必须在请求在其客户区域中绘制时绘制自己的背景。要确定是否必须绘制背景,应用程序可以处理WM_ERASEBKGND消息或测试fErase函数填充的PAINTSTRUCT结构的BeginPaint成员。

    这意味着默认窗口过程没有响应WM_ERASEBKGND消息,因为您没有给窗口提供背景画笔。

    您需要将hbrBackground设置为COLOR_WINDOW + 1之类的内容,否则您需要向WM_ERASEBKGND消息处理程序添加代码以自行删除窗口的背景。

  3. 或者,或许更好的选择是完全忘记WM_ERASEBKGND消息,正如许多Windows程序员所做的那样,因为这种两步擦除和绘制方法往往会导致闪烁。将hbrBackground字段设置为NULL,不响应WM_ERASEBKGND消息执行任何操作,并在WM_PAINT处理程序的顶部执行删除操作:

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
    
        // Erase background of entire client area.
        RECT rcClient;
        GetClientRect(hWnd, &rcClient);
        FillRect(hdc, &rcClient, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
    
        // Do normal drawing.
        Ellipse(hdc, x, y, width, height);
    
        EndPaint(hWnd, &ps);
        return 0;
    }