GDI graphics

Hello,

I just started out with win32 programming a week ago, been reading some book to help me out. I have encountered some difficulities with rendering graphics in GDI:

In the WndProc fnc ptr:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case WM_PAINT:
		{
			// simply validate the window
			InvalidateRect(hwnd,NULL,FALSE);
			hdc = BeginPaint(hwnd,&ps);

			TCHAR buffer[80];
			static int iWmPaintCount = 0;
			// set the foreground color to blue
			SetTextColor(hdc, RGB(0,0,255));
			// set the background color to black
			SetBkColor(hdc, RGB(0,0,0));
			// finally set the transparency mode to transparent
			SetBkMode(hdc, OPAQUE);
			// draw some text at (0,0) reflecting number of times
			// wm_paint has been called
			_stprintf_s(buffer,_T("WM_PAINT called %d times"), ++iWmPaintCount);
			TextOut(hdc, 0,0, buffer, _tcslen(buffer));

			EndPaint(hwnd,&ps);

			return 0;
		}


Now: It seems like not every WM_PAINT message causes the window to be repainted, 'sometimes' it repaints and sometimes the 'output' just jumps 50 WM_PAINT messages at once, not repainting on 50 messages.

Am I doing something wrong?

Thx

----------------------------------
EDIT: I also have another problem:
In WM_PAINT:
1
2
3
4
5
6
7
8
9
case WM_PAINT:
		{
			// simply validate the window
			InvalidateRect(hwnd,NULL,FALSE);
			hdc = BeginPaint(hwnd,&ps);
			EndPaint(hwnd,&ps);

			return 0;
                 }

In WinMain:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while(true)
	{
		DWORD dwStartTime = GetTickCount();

		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
			{break;}

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		HDC hdc = GetDC(hwnd1);

		COLORREF pixelColor = RGB(rand()%256,rand()%256,rand()%256);
		int x = rand()%800; int y = rand()%600;
		SetPixel(hdc,x,y,pixelColor);

		ReleaseDC(hwnd1,hdc);
	}


This draws pixels in the client area with a random colour at a random location.

It works, but when I move the client area of the window 'beyond' my screen, and then drag it back, the pixels that were previously painted don't show anymore, and are now replaced by the background colour of the window. Is there any way to prevent this? Thank you!
Last edited on

Am I doing something wrong?


No. Windows optimizes WM_PAINT messages in the message queene into one message and executes that one repaint.

Don't put InvalidateRect() calls in a WM_PAINT handler. It'll likely cause a pretty bad internal foulup of some sort. When one WM_PAINT executes (the purpose of which is to validate the invalid rectangle), it will immediately be invalidated again by your InvalidateRect() call.
Also, the thing you are doing down in WinMain() with GetDC() is absolutely wrong! I see this over and over and over again by beginners, that is, using GetDC in such a manner. In my opinion GetDC ought to be 'outlawed' for all those with less than 1 year of Windows programming experience. You need to learn to do ALL drawing in WM_PAINT message handlers. Then what you paint will persist.
I am aiming towards game programming, so doing the painting in WM_PAINT is not so great for this since it is event based.

Also, the reason why you told me not to use InvalidateRect() is exactly the reason I am using it for. I want to get access to the entire client area, not what is invalidated by default. If I were to leave out the InvalidateRect() there, my problem doesn't get solved at all, it only gets worse...

When I am moving the window without the InvalidateRect() function, it doesn't repaint anything at all, unless I move the window 'out of the screen'. With the InvalidateRect() function, it repaints while moving even when the window is not hindered by anything, though not always, which I found weird. That's what my (first) question is.

EDIT: Also, you say the way I use GetDC() is wrong, yet it is used in a book to explain windows programming for game programmers. Neither do you give me a reason, just tell me that it's 'bad'. I would love to get some information on this instead of learning ethics. Thank you.
Last edited on
@ OP: Game programming is harder than regular windows programming, and you are a beginner at both. First understand regular windows programming and the GDI before attempting to code a game.

It is wrong to use InvalidateRect() inside WM_PAINT. It will screw up everything. Don't. Instead, call InvalidateRect() from the game event and let WM_PAINT do the rest.

Once you understand this, then start experimenting with GetDC() and the benefits/losses of this.
I am aiming towards game programming, so doing the painting in WM_PAINT is not so great for this since it is event based.


Games shouldn't be using WinGDI for graphics anyway. It's too slow.

Make it easier on yourself and get a game lib (I recommend SFML: http://www.sfml-dev.org/ ). Much easier, and faster.

It also has the benefit of being cross platform.
I have already coded some 2D games in C++ using a custom made engine based on windows GDI + Direct2D( but these were kind of hidden to us internally ), so I know a little.

I know GDI is slow, and am learning Direct3D aswell now, but I think it's good to have a solid knowledge about the GDI aswell, no?
I like to have some knowledge of what I am doing, and this has been made really hard by windows, since it doesn't allow writing directly to the hardware. Yes, it's easier, safer, and more time-efficient, but it's also harder to understand what is actually going on.

I still don't know why using InvalidateRect() is wrong... The way I use it, it's supposed to invalidate the entire client area again, repaint it, and use endpaint() to validate the window again. Do I understand this correctly?

Also, none of my questions asked in the original post of the thread have yet properly been answered :(
Last edited on
The WM_PAINT message is one of a number of 'special' messages in Windows. Since Windows is a graphical system, anything pertaining to rendering graphics is pretty critically important. Also, its a multitasking system, and its design is such that many processes/threads can hopefully be running at the same time. And of course, there is only so much 'horsepower' to spare, so what graphics processing it does must be efficient. You were seeing a manifestation of that when you noted that every WM_PAINT message wasn't resulting in a WM_PAINT call. Windows actually goes through the message quene accumulating the update regions of WM_PAINT messages, then it processes just one. That saves it time and allows more resources for other processes.

What you are doing with putting an InvalidateRect() call in a WM_PAINT handler is causing an infinite loop in that Windows can't get out of the Paint handler. Every InvalidateRect() call is causing another WM_PAINT message to be put into the message queene (however one spells it), so it can't ever get out of it. If you check your task manager while your program is running you'll likely find the processor is pinned at 100% and your system is all but unusable. If that's how you want your programs to work then all I can say is 'have it your way'.

In terms of GetDC(), yes, you can draw on the screen with it. However, only output drawn during a WM_PAINT message will persist. This is an architectural thing; its how Windows was designed and functions. Its sort of a brute fact of existance or reality, one of those things you can't change. You are already seeing this. You are not going to be able to change it. There is no change you can make to your code to alter it. Its just the way it is.
It's only taking 20%... Okay, seriously now: this is what the book says...

Book Name: "Tricks of the Windows Game Programming Gurus, Second Edition"

Now, if you want to do your own graphics within the BeginPaint()—EndPaint() call, you can. However, there is one problem: You will only have access to the portion of the window's client area that actually needs repainting. The coordinates of the invalid rectangle are stored in the rcPaint field of the ps (PAINSTRUCT) returned by the call to BeginPaint()

In other words, referring back to Figure 3.12, the window is 400x400, but only the lower region of the window—300,300 to 400,400—needs repainting. Thus, the graphics device context returned by the call to BeginPaint() is only valid for this 100x100 region of your window! Obviously, this is a problem if you want to have access to the entire client area.

The solution to the problem has to do with gaining access to the graphics device context for the window directly without it being sent as part of a window update message via BeginPaint(). You can always get a graphics context for a window or hdc using the GetDC() function, as shown here:

HDC GetDC(HWND hWnd); // handle of window

You simply pass the window handle of the graphics device context you want to access, and the function returns a handle to it. If the function is unsuccessful, it returns NULL. When you're done with the graphics device context handle, you must give it back to Windows with a call to ReleaseDC()

However, there is one problem: When you make a call to GetDC()—ReleaseDC(), Windows has no idea that you have restored or validated the client area of your window. In other words, if you use GetDC()—ReleaseDC() in place of BeginPaint()—EndPaint(), you'll create another problem!

The problem is that BeginPaint()—EndPaint() sends a message to Windows indicating that the window contents have been restored (even if you don't make any graphics calls). Hence, Windows won't send any more WM_PAINT messages. On the other hand, if you replace BeginPaint()—EndPaint() with GetDC()—ReleaseDC() in the WM_PAINT handler, WM_PAINT messages will continue to be sent forever! Why? Because you must validate the window.

To validate the area of a window that needs repainting and tell Windows that you have restored the window, you could call BeginPaint()—EndPaint() after the call to GetDC()—ReleaseDC(), but this would be inefficient. Instead, use the call specifically designed for this, called ValidateRect()

Consider this: If you could somehow invalidate the entire window within your WM_PAINT handler, you would be sure that the rcPaint field of the ps PAINTSTRUCT returned by BeginPaint() and the associated gdc would give you access to the entire client area of the window. To make this happen, you can manually enlarge the invalidated area of any window with a call to InvalidateRect(), as shown here:

BOOL InvalidateRect(HWND hWnd, // handle of window with
// changed update region
CONST RECT *lpRect, // address of rectangle coordinates
BOOL bErase); // erase-background flag

If bErase is TRUE, the call to BeginPaint() fills in the background brush; otherwise, it doesn't.

Simply call InvalidateRect() before the BeginPaint()—EndPaint() pair, and then, when you do call BeginPaint(), the invalid region will reflect the union of what it was and what you added to it with the InvalidatRect(). However, in most cases, you will use NULL as the lpRect parameter of InvalidateRect(), which will invalidate the entire window. Here's the code:

PAINTSTRUCT ps; // used in WM_PAINT
HDC hdc; // handle to a device context

case WM_PAINT:
{
// invalidate the entire window
InvalidateRect(hwnd, NULL, FALSE);
// begin painting
hdc = BeginPaint(hwnd,&ps);
// you would do all your painting here
EndPaint(hwnd,&ps);
// return success
return(0);
} break;


Plz read before commenting
Last edited on
Topic archived. No new replies allowed.