Using GDI+ to draw lines, fill shapes and write text on a UserForm
- John
- Oct 14
- 5 min read
Updated: 6 hours ago
This post is one of a series providing implementation examples of Windows API Functions, Types, Enums and Consts using VBA. The code in this post can be used as-is, however, if you regularly (or even just occasionally) work with Windows API declarations in VBA, you may want to see the posts Automatically add Windows API declaration(s) and Using 'F1' to view Windows API web pages which explain some of the functionality that can be added to the VBE by VBE_Extras.
GDI+ (Graphics Device Interface Plus) is a part of the Windows API that allows drawing lines, filling shapes and writing text on windows (actually using the Device Context from a window) ... in this example, drawing on a UserForm window ... like this ...

I know ... my art skills are not up to much ... but it's just a demo!
GDI+ contains many hundreds of Functions along with a number of Types, Enums and Consts. This example only covers 30 Functions plus the Types, Enums and Consts associated with those 30 Functions so I'm not suggesting that the code provided here is the only way to use GDI+. As ever, explore the documentation to learn more ...
General information on GDI plus is here: https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-gdi-start
The GDI+ reference documentation is here: https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-class-gdi-reference ... however, note that documentation for GDI+ is not as good as the documentation for much of the rest of the Windows API with some Functions being entirely undocumented and the documentation for others not being entirely accurate
I won't provide a detailed explanation of how to use each of the 30 Functions ... but the code provided here is commented for your understanding. I will outline, though, the general process for initialising, using and then closing GDI+, as follows:
Initialising GDI+ ...
Get a handle to the window that you want to draw on ... using FindWindow etc
Get a Device Context for that window ... using GetDC and the previously obtained window handle
Start GDI+ ... using GdiplusStartup
Create a Graphics object ... in this case, using GdipCreateFromHDC and the previously obtained Device Context ... the Graphics object is then used by all of the subsequent drawing and filling of lines, shapes and text
Using GDI+ ...
If you want to draw lines ...
Create a Pen object ... in this case, using GdipCreatePen1
Optionally, update the properties of the Pen object using the various GdipSetPenXxx Functions (eg GdipSetPenColor)
Draw lines with the Pen object using the various GdipDrawXxx Functions (eg GdipDrawLine)
If you want to fill shapes and/or draw text ...
Create a Brush object ... in this case, using GdipCreateSolidFill
Optionally, update the properties of the Brush object using the various GdipSetSolidFillXxx Functions (eg GdipSetSolidFillColor)
Fill shapes with the Brush object using the various GdipFillXxx Functions (eg GdipFillEllipse)
If you want to draw text ...
Create a FontFamily object ... using GdipCreateFontFamilyFromName
Create a Font object ... using GdipCreateFont and the previously created FontFamily object
Create a StringFormat object ... using GdipCreateStringFormat
Optionally set other properties of the StringFormat object using the various GdipSetStringFormatXxx Functions (eg GdipSetStringFormatAlign)
Create a location in which to draw text ... in this case, a RECTF
Draw the text ... using the GdipDrawString Function and the previously created RECTF and the Font and StringFormat objects
Closing GDI+ ...
If you created a Pen object ... destroy it using GdipDeletePen
If you created a Brush object ... destroy it using GdipDeleteBrush
Destroy the Graphics object ... using GdipDeleteGraphics
Release the Device Context ... using ReleaseDC
Stop GDI+ ... using GdiplusShutdown
... which is all a bit verbose ... and so I created the CGdiPlusUtils Class (code below) which simplifies this ...
Initialising CGdiPlusUtils ...
Create an instance of CGdiPlusUtils
Call InitialiseForUserForm (if wanting to use GDI+ with a UserForm) or InitialiseForWindow (for any other type of window)
Using CGdiPlusUtils ...
If you want to draw lines ...
Optionally, update the properties of the Pen object using the various UpdatePenXxx Functions (eg UpdatePenColour)
Draw lines with the Pen object using the various DrawXxx Functions (eg DrawLine)
If you want to fill shapes ...
Optionally, update the properties of the Brush object using the various UpdateBrushXxx Functions (eg UpdateBrushColour)
Fill shapes with the Brush object using the various FillXxx Functions (eg FillEllipse)
If you want to draw text ...
Optionally, update the properties of the Brush object using the various UpdateBrushXxx Functions (eg UpdateBrushColour)
Draw the text ... using the DrawString Function
Closing CGdiPlusUtils ...
... is handled for you
So hopefully this is simpler to use and less bug-prone than using the GDI+ Functions directly in your own code.
So here's the code of CGdiPlusUtils ... it's a lot of code but, as noted above, it's documented for your understanding. Note you can also download an entire Workbook including CGdiPlusUtils (and the other code in this post) at the end of the post ...
... note that, as its name suggests, CGdiPlusUtils is the name of a Class Module ... if copy/pasting this code you must paste it into a Class Module named CGdiPlusUtils.
Also notable here is that the DrawString method could have many more parameters to control other aspects of how the text is drawn (eg for the String Alignment, StringFormatFlags, the size of the drawing rectangle etc). I've limited the number of parameters (and so the overall functionality) here as this, after all, just a demo of GDI+. Feel free to extend and enhance this (and any other part of this code!) ... if doing so, other posts that might help are:
Showing the 'Font' dialog box using the ChooseFont Windows API Function to allow the user to select a specific Font name, size and other properties
Get a list of all Fonts installed on a device using the EnumFontFamiliesEx Windows API Function to check whether a specific Font exists on a device or, maybe, if you wanted to give the user a choice of Font names only
This, then, is the code in the UserForm that uses CGdiPlusUtils. If creating this yourself, I used a UserForm named "UserForm1" that is 427 points wide by 267 point high and (as you can see from the image at the start of the post) has 9 CommandButtons, the click event of which hooks up to the relevant (based on the text on the CommandButton) Xxx_Click() method in the following code ...
Finally, this is some code in a standard Module named Module1 which creates an instance of the UserForm and shows it (if you've added all the code manually ... well done ... you can now run the ShowUserForm method).
The non-GDI+ code that does need a bit of explanation is that associated with the SetTimer / KillTimer Windows API Functions. The reason for this is associated with the two lines (upper / left) that are already drawn on the UserForm as soon as it is shown (as opposed to all the drawing that is done subsequently using the CommandButtons). In order to do this (ie draw on the UserForm immediately that it is shown), I'm effectively creating a new UserForm event ... one that runs after the UserForm is shown (but runs so quickly after it is shown that the delay isn't noticeable at all).
The "event" is setup by the DoAfterShowing method in the standard Module (which is called by the Display method in the UserForm after it has created an instance of CGdiPlusUtils and right before Show is called) ... this creates a timer using SetTimer with a 1 millisecond delay. This means that immediately after the UserForm is shown (and so immediately after all of the UserForm's own setup and drawing is finished), the timer triggers and the AfterShowingCallback method is called. AfterShowingCallback then calls the DrawLines method within the UserForm (as the UserForm has the CGdiPlusUtils object) which draws the first two (upper / left) lines.
None of this complexity is required, of course, if you only want to draw on the UserForm some time after it is shown (for example, when responding to CommandButton click events).
To save copy/pasting (and creating 9 CommandButtons), here's the Excel workbook ...
... once opened, click the button on Sheet1 to show the UserForm.



Comments