top of page

Using GDI+ to draw lines, fill shapes and write text on a UserForm

  • Writer: John
    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 ...


A UserForm ... that has been drawn on

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 ...



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:



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


bottom of page