• Advertisement
Sign in to follow this  

Drawing with GDI works, but I can't do the same thing in GDI+

This topic is 907 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have two buttons; namely "Start" and "Stop". My aim is to add icons on them. I subclassed them, then painted with GDI functions. It worked perfectly as expected. Then I tried to do the same thing using GDI+ functions only on the "Start" button. I was going to switch to GDI+ functions on the "Stop" button as well, if I could had succeeded on the "Start" button, but I couldn't. Now, my two buttons look like in the screen shot below.

 

JThmQz9.png

 

"Stop" button's icon is painted successfully, but "Start" button's icon is not.

 

My child window message processor function is as seen below. Note that, the GDI+ code is not currently printing the icon; it desperately tries to draw a simple line.

 

What am I doing wrong here? Why don't my GDI+ functions work?

LRESULT CALLBACK WindowMain::ChildWindowProc(	HWND		hWnd,
						UINT		uMsg,
						WPARAM		wParam,
						LPARAM		lParam,
						UINT_PTR	uIdSubclass,
						DWORD_PTR	dwRefData)
{
	//static const HBRUSH hPlayActive	= CreateSolidBrush(RGB(20,	255,	40));
	//static const HBRUSH hPlayInactive	= CreateSolidBrush(RGB(10,	40,	20));
	static const HBRUSH hStopActive		= CreateSolidBrush(RGB(255,	20,	40));
	static const HBRUSH hStopInactive	= CreateSolidBrush(RGB(40,	10,	20));

	switch (uMsg)
	{
		case WM_PAINT:
		{
			LRESULT lResult = DefSubclassProc(hWnd, uMsg, wParam, lParam);
			switch (uIdSubclass)
			{
				case ID_BT_START:
				{
					// The code below doesn't work.
					Gdiplus::GdiplusStartupInput	gdiplusStartupInput;
					ULONG_PTR			gdiplusToken;
					Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
					PAINTSTRUCT                     ps;
					Gdiplus::Status                 Error;
					HDC hdc = BeginPaint(hWnd, &ps);
					{
						Gdiplus::Graphics	graphics(hdc);
						Gdiplus::Pen		pen(Gdiplus::Color(255, 0, 0, 255));
						Error = graphics.DrawLine(&pen, 0, 0, 20, 10);    // Always returns "0 (Ok)".
					}
					EndPaint(hWnd, &ps);
					Gdiplus::GdiplusShutdown(gdiplusToken);
					EndPaint(hWnd, &ps);

					// It works if I use the commented out code block below.
					/*static const size_t	VERTEX_COUNT	= 3;	// Number of vertexes. (3 for triangle.)
					static const int	LEFT_X		= 18;
					static const int	TOP_Y		= 8;
					static const int	WIDTH		= 14;
					static const int	HEIGHT		= 14;
					static const std::array<int, VERTEX_COUNT> VERTEXES{    LEFT_X,		TOP_Y			},
												LEFT_X,		TOP_Y + HEIGHT		},
										                LEFT_X + WIDTH,	TOP_Y + HEIGHT / 2	}};
					HDC hDC = GetDC(hWnd);
					if (IsWindowEnabled(hWnd))
					{
						SelectObject(hDC, hPlayActive);
					}
					else
					{
						SelectObject(hDC, hPlayInactive);
					}
					PolyPolygon(hDC, &(VERTEXES[0]), &VERTEX_COUNT, 1);
					ReleaseDC(hWnd, hDC);*/
					break;
				}
				case ID_BT_STOP:
				{
					// The following legacy GDI version works as expected.
					static const int LEFT_X			= 18;
					static const int TOP_Y			= 8;
					static const int WIDTH			= 14;
					static const int HEIGHT			= 14;
					static const int RADIUS_X		= 2;
					static const int RADIUS_Y		= 2;
					HDC hDC = GetDC(hWnd);
					if (IsWindowEnabled(hWnd))
					{
						SelectObject(hDC, hStopActive);
					}
					else
					{
						SelectObject(hDC, hStopInactive);
					}
					RoundRect(hDC, LEFT_X, TOP_Y, LEFT_X + WIDTH, TOP_Y + HEIGHT, RADIUS_X, RADIUS_Y);
					ReleaseDC(hWnd, hDC);
					break;
				}
				default:
					return DefSubclassProc(hWnd, uMsg, wParam, lParam);
			}
			return lResult;
			break;
		}
		case WM_DESTROY:
			switch (uIdSubclass)
			{
				case ID_BT_START:
				{
					//DeleteObject(hPlayActive);
					//DeleteObject(hPlayInactive);
					break;
				}
				case ID_BT_STOP:
				{
					DeleteObject(hStopActive);
					DeleteObject(hStopInactive);
					break;
				}
			}
			return DefSubclassProc(hWnd, uMsg, wParam, lParam);
			break;
		default:
			return DefSubclassProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
}
Edited by hkBattousai

Share this post


Link to post
Share on other sites
Advertisement

Don't call startup and shutdown on GDI+ on every draw.. call startup once when starting up your program and then shutdown once when the program exits.

Also you call EndPaint twice in your code.

 

Lastly your working examples use GetDC while the GDI+ version uses BeginPaint, perhaps DefSubclassProc already clears the dirty rect so nothing is left to draw (the DC from Begin/End-Paint will mask to only draw to areas where it thinks the window requires an update, and set that area to not requiring updates anymore so the next draw will do nothing).

Share this post


Link to post
Share on other sites

Don't call startup and shutdown on GDI+ on every draw.. call startup once when starting up your program and then shutdown once when the program exits.

Also you call EndPaint twice in your code.

 

Lastly your working examples use GetDC while the GDI+ version uses BeginPaint, perhaps DefSubclassProc already clears the dirty rect so nothing is left to draw (the DC from Begin/End-Paint will mask to only draw to areas where it thinks the window requires an update, and set that area to not requiring updates anymore so the next draw will do nothing).

 

You were right in all points. The source of my problem was as you said, BeginPaint() was not returning the device context correctly. Now my code works correctly. Thank you.

 

eZqJlFG.png

LRESULT CALLBACK WindowMain::ChildWindowProc(	HWND		hWnd,
						UINT		uMsg,
						WPARAM		wParam,
						LPARAM		lParam,
						UINT_PTR	uIdSubclass,
						DWORD_PTR	dwRefData)
{
	switch (uMsg)
	{
		case WM_PAINT:
		{
			LRESULT lResult = DefSubclassProc(hWnd, uMsg, wParam, lParam);
			switch (uIdSubclass)
			{
				case ID_BT_START:
				{
					static const size_t	VERTEX_COUNT	= 3;	// Number of vertexes. (3 for triangle.)
					static const int	LEFT_X		= 18;
					static const int	TOP_Y		= 8;
					static const int	WIDTH		= 14;
					static const int	HEIGHT		= 14;
					static const std::array<Gdiplus::Point, VERTEX_COUNT> VERTEXES{	Gdiplus::Point{LEFT_X,		TOP_Y			},
													Gdiplus::Point{LEFT_X,		TOP_Y + HEIGHT		},
													Gdiplus::Point{LEFT_X + WIDTH,	TOP_Y + HEIGHT / 2	}};

					PAINTSTRUCT  ps;
					HDC hDC	= BeginPaint(hWnd, &ps);	// Doesn't return "hDC" correctly.
					hDC	= GetDC(hWnd);			// So, I get the correct "hDC" like this.

					{
							Gdiplus::Graphics	graphics    (hDC);
						const	Gdiplus::Pen		pen	    (Gdiplus::Color(255, 0,   0,   0));
						const	Gdiplus::SolidBrush	brshActive  (Gdiplus::Color(255, 20,  255, 40));
						const	Gdiplus::SolidBrush	brshInactive(Gdiplus::Color(255, 10,  40,  20));
						if (IsWindowEnabled(hWnd))
						{
							graphics.FillPolygon(&brshActive,	&VERTEXES[0], VERTEX_COUNT);
						}
						else
						{
							graphics.FillPolygon(&brshInactive,	&VERTEXES[0], VERTEX_COUNT);
						}
						graphics	.DrawPolygon(&pen,		&VERTEXES[0], VERTEX_COUNT);
					}

					EndPaint(hWnd, &ps);
					break;
				}
				case ID_BT_STOP:
				{
					static const int LEFT_X		= 18;
					static const int TOP_Y		= 8;
					static const int WIDTH		= 14;
					static const int HEIGHT		= 14;
					static const int RADIUS_X	= 4;
					static const int RADIUS_Y	= RADIUS_X;

					PAINTSTRUCT  ps;
					HDC hDC	= BeginPaint(hWnd, &ps);		// Doesn't return "hDC" correctly.
					hDC	= GetDC(hWnd);				// So, I get the correct "hDC" like this.

					{
						Gdiplus::Graphics Graphics(hDC);
                                                const    Gdiplus::Pen           Pen         (Gdiplus::Color(255, 0,   0,   0));
                                                const    Gdiplus::SolidBrush    brshActive  (Gdiplus::Color(255, 255, 20,  40));
                                                const    Gdiplus::SolidBrush    brshInactive(Gdiplus::Color(255, 40,  10,  20));
                                                if (IsWindowEnabled(hWnd))
                                                {
                                                    DrawAndFillRoundedRectangle(Graphics, Pen, brshActive,   LEFT_X, TOP_Y, WIDTH, HEIGHT, RADIUS_X, RADIUS_Y);
                                                }
                                                else
                                                {
                                                    DrawAndFillRoundedRectangle(Graphics, Pen, brshInactive, LEFT_X, TOP_Y, WIDTH, HEIGHT, RADIUS_X, RADIUS_Y);
                                                }
					}

					EndPaint(hWnd, &ps);
					break;
				}
			}
			return lResult;
			break;
		}
		default:
			return DefSubclassProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
}

void WindowMain::DrawAndFillRoundedRectangle(	        Gdiplus::Graphics &	Graphics,
						const	Gdiplus::Pen &		Pen,
						const	Gdiplus::SolidBrush &	Brush,
							INT			X_LEFT,
							INT			Y_TOP,
							INT			WIDTH,
							INT			HEIGHT,
							INT			X_RADIUS,
							INT			Y_RADIUS)
{
	const INT   ArcWidth	= 2 * X_RADIUS;
	const INT   ArcHeigth	= 2 * Y_RADIUS;
	const float SweepAngle	= -90.0f;		// Stupid GDI+ sweeps in the negative direction.
							// The minus sign is to compensate it.
	Gdiplus::GraphicsPath Path;
	
	// Top-Left Arc
	Path.AddArc(	X_LEFT,
			Y_TOP,
			ArcWidth, ArcHeigth,
			-90.0f, SweepAngle);

	// Left Edge
	Path.AddLine(	X_LEFT,
			Y_TOP			+ Y_RADIUS,
			X_LEFT,
			Y_TOP	+ HEIGHT	- Y_RADIUS);

	// Bottom-Left Arc
	Path.AddArc(	X_LEFT,
			Y_TOP	+ HEIGHT	- ArcHeigth,
			ArcWidth, ArcHeigth,
			-180.0f, SweepAngle);

	// Bottom Edge
	Path.AddLine(	X_LEFT			+ X_RADIUS,
			Y_TOP	+ HEIGHT,
			X_LEFT	+ WIDTH		- X_RADIUS,
			Y_TOP	+ HEIGHT);

	// Bottom-Right Arc
	Path.AddArc(	X_LEFT	+ WIDTH		- ArcWidth,
			Y_TOP	+ HEIGHT	- ArcHeigth,
			ArcWidth, ArcHeigth,
			-270.0f, SweepAngle);

	// Right Edge
	Path.AddLine(	X_LEFT	+ WIDTH,
			Y_TOP	+ HEIGHT	- Y_RADIUS,
			X_LEFT	+ WIDTH,
			Y_TOP			+ Y_RADIUS);

	// Top-Right Arc
	Path.AddArc(	X_LEFT	+ WIDTH		- ArcWidth,
			Y_TOP,
			ArcWidth, ArcHeigth,
			0.0f, SweepAngle);

	// Top Edge
	Path.AddLine(	X_LEFT	+ WIDTH		- X_RADIUS,
			Y_TOP,
			X_LEFT			+ X_RADIUS,
			Y_TOP);
	
	Graphics.FillPath(&Brush,	&Path);
	Graphics.DrawPath(&Pen,		&Path);
}

Share this post


Link to post
Share on other sites

Note that it does return the DC correctly, it's just not flagged for update at that time.

 

For example, if you have a large window of say 500 x 500 pixels with an image in it, and you drag another window above its top left corner covering an area of 100x100 pixels, then WM_PAINT will be sent by Windows and BeginPaint will return a DC that will only paint on those 100x100 pixels that were invalidated by the other window.

The reason for this is so that no unnecessary drawing occurs.

 

If you want to flag a specific area for update and receive a WM_PAINT you can do so with InvalidateRect.

 

I would guess that the DefSubclassProc handles WM_PAINT and calls Begin/End-Paint and as such validates the update-rect, so that your BeginPaint call returns a DC with an empty update-rect, clipping all your draw-commands.

 

You can read about this on MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183362%28v=vs.85%29.aspx

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement