WinAPI. Can't erase window background with ellipse
I am just trying to draw an ellipse:
case WM_PAINT:
hdc = BeginPaint(parentWindow, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(parentWindow, &ps);
and then remove it with a new ellipse with new parameters every second using a timer:
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
break;
case WM_TIMER:
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
break;
But the ellipses are not erased and are layered:
However, I tried to keep track of WM_ERASEBKGND and it does indeed get sent every InvalidateRect.
Complete code:
#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);
}
}
source to share
Your code doesn't erase anything. It just draws an ellipse at the specified coordinates. The previously drawn ellipse still exists.
You mentioned the post WM_ERASEBKGND
, but there are two reasons why this doesn't work for you:
-
In your window procedure (
WndProc
), you handle the messageWM_ERASEBKGND
explicitly, which means it does not go to the default window procedure (DefWindowProc
). The best way to write your window procedure would be as follows: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); }
Now the default window procedure is called every time unless you explicitly specify
return
inside the labelcase
. -
When you register your window class (internally
MyRegisterClass
), you zero out all the fields of the structureWNDCLASS
and then explicitly initialize a couple of them. You are not explicitly initializing the fieldhbrBackground
, so it is set to 0. And when ithbrBackground
is 0,When this member is given
NULL
, the application must paint its own background whenever it is asked to paint in its client area. To determine if the background should be colored, the application can process the messageWM_ERASEBKGND
or check thefErase
structure memberPAINTSTRUCT
filled with the functionBeginPaint
.This means that the default window procedure does nothing in response to the message
WM_ERASEBKGND
because you didn't give your window a background brush.You either need to set
hbrBackground
to something likeCOLOR_WINDOW + 1
, or you will need to add code to the message handlerWM_ERASEBKGND
to erase the background of the background yourself.
Or perhaps an even better option would be to forget about the message WM_ERASEBKGND
altogether, as many Windows programmers do, because this two-step erase and paint method tends to cause flicker. Leave the field hbrBackground
NULL, do nothing in response to the message, WM_ERASEBKGND
and remove it at the top of the handler 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;
}
source to share