A Visual Basic Paint Program

Visual Basic has an extensive range of features for creating graphics. Using the appropriate commands, it is possible to draw shapes and lines, to create and manipulate images, to intermingle text and graphics, and to utilise a vast range of colours. The subject of computer graphics, and of the graphics capabilities of Visual Basic in particular, is worthy of a book or website in its own right.

We will not delve too deeply into the capabilities of Visual Basic's graphics library in these pages. We will however explore some of its useful features by developing a fully functional application for creating graphic images. The program is based on the Microsoft Windows Paint program. It allows the user to create simple graphic images and save them to a file.

The functionality of the program has been limited in order to simplify the design of the interface and reduce the amount of coding required, so there is plenty of scope for further experimentation. Even so, the application is far from trivial, so we have a fair amount of work to do.

  1. Open a new project called "EasyPaint".
  2. In the Solution Explorer window, rename the file "Form1.vb" to "frmEasyPaint.vb".
  3. Set the application's Startup Object property to EasyPaint.frmEasyPaint.
  4. Set the form's properties as follows:

Main Form Properties
PropertyValue
Text"EasyPaint"
Size640, 600

  1. In the toolbox, expand the Menus & Toolbars pane, and double-click the MenuStrip item to create a menu strip control on your form (by default, the menu strip will dock with the top of your form).
  2. In the control's Properties window, rename the menu strip control from "MenuStrip1" to "mnuEasyPaint".
  3. Click on the menu strip (where it says "Type Here") and type the word "File" to create the application's File menu.
  4. As you type, you will see a second text box appear below and slightly to the right of the first one. In this box, type the word "New" to add this item to the application's File menu.
  5. Repeat the previous step two more times to add the items "Save" and "Exit" to the File menu (don't worry about the code for these menu items just yet - we will be adding it later).
  6. In the toolbox, expand the Dialogs pane and double-click the FontDialog item. When when the font dialog control appears below your form, rename it from "FontDialog1" to "dlgFont".
  7. Repeat the previous step to create a SaveFileDialog control, and rename it from "SaveFileDialog1" to "sfdSavePic".
  8. In the toolbox, expand the Containers pane and double-click the Panel item (you will find this under All Windows Forms) to place a panel control on your form.
  9. Set the properties of the panel control as shown in the table below.

pnlMain Control Properties
PropertyValue
NamepnlMain
EnabledFalse
Location0, 24
Size624, 538

  1. Create a second panel control and set its properties as shown below (Caution: before you create the panel, make sure that the object currently selected in the form designer is the application's main form - this is important to ensure that the new panel becomes a child of the main form, not the first panel (a control's location is always specified relative to its parent control).

pnlFrame Control Properties
PropertyValue
NamepnlFrame
BorderStyleFixedSingle
Location9, 49
Size482, 482

  1. Now create a PictureBox control on your form (see the cautionary note above) with the following properties:

picCanvas Control Properties
PropertyValue
NamepicCanvas
BackColorGray
Location10, 50
Size480, 480

Your form should now look like the illustration below.


The EasyPaint program interface

The EasyPaint program interface


The form does not yet have much in the way of controls, but we have created a drawing area. The drawing area is is defined by the PictureBox control picCanvas, which will be assigned a bitmap image. It is the bitmap image, rather than the control itself, that we will draw on. This means that turning the images we create into files is relatively easy (we will elaborate on how this is done later). The PictureBox control serves both to define the boundaries of the drawing area and to provide a background for the lines, shapes, text, and freehand brush strokes drawn by the user.

The pnlMain control sits behind all of the other controls on the form (including those not yet created) except for the mnuEasyPaint control, and effectively becomes the parent object for them. We have intentionally disabled this panel so that when the user opens the application the only control that will be available to them is the File menu.

The pnlFrame control sits behind picCanvas, and is larger by two pixels in each direction. Its sole purpose is to provide a frame for picCanvas, and its BorderStyle property is set to FixedSingle, whereas that of picCanvas is set to None (we will explain the reason for this later).

We will now create controls to allow the user to choose the text size and font for their drawings (note that the remaining controls will be assigned coordinates relative to the pnlMain control).

  1. With the pnlMain control selected, go to the toolbox and double-click on the Label item.
  2. Set the Label control's properties as follows:

lblEditText Control Properties
PropertyValue
NamelblEditText
Location12, 6
TextInsert text:

  1. Make sure the pnlMain control is selected, then go to the toolbox and double-click the TextBox item.
  2. Set the TextBox control's properties as follows:

txtInsertText Control Properties
PropertyValue
NametxtInsertText
Location75, 3
Size225, 20

  1. With pnlMain selected, go to the toolbox and double-click the Label item.
  2. Set the Label control's properties as follows:

lblFontDetails Control Properties
PropertyValue
NamelblFontDetails
Location317, 6
Text (Arial 12pt, Regular)

  1. With pnlMain selected, go to the toolbox and double-click the Button item.
  2. Set the Button control's properties as follows:

cmdFont Control Properties
PropertyValue
NamecmdFont
Location512, 485
Size97, 23
TextSelect Font

Later, we will be adding some code to the cmdFont button that opens the FontDialog control to allow the user to select from the fonts available on their computer. The selection will be reflected in the contents of the label control lblFontDetails, which is set to display "(Arial 12pt, Regular)" by default.

The text box will be used to enter any text that the user wants to insert into their image. The programming can wait for the moment, however. Next, we will create the controls that allow the user to select a colour for whatever tool they are currently using. For this application, we have limited the palette to sixteen colours, but there is no reason it could not be extended to the full range of available colours (up to a little over sixteen million colours, in fact!) with a little more work.

  1. With the pnlMain control selected, go to the toolbox and double-click the Label item.
  2. Set the Label control's properties as follows:

lblBlack Control Properties
PropertyValue
NamelblBlack
AutoSizeFalse
BackColorBlack
BorderStyleFixedSingle
Location499, 47
Size23, 23
TextNone

This control is the first of sixteen colour selector controls. The remaining controls will share many of the same property values, with the only differences between them being the Name, BackColor and Location properties.

  1. Copy and paste the lblBlack control onto the picCanvas PictureBox control to create fifteen new colour selector controls and drag them into approximately the right position (see the illustration further down this page). Set the properties as specified in the table below. Note: make sure the picCanvas control is selected before you paste each copy, or you will probably find it has been pasted behind the picCanvas control and you will not be able to drag it into position.

Colour Selector Control Properties
NameBackColorLocation
lblNavyNavy528, 47
lblGreenGreen557, 47
lblTealTeal586, 47
lblMaroonMaroon499, 82
lblPurplePurple528, 82
lblOliveOlive557, 82
lblSilverSilver586, 82
lblGrayGray499, 117
lblBlueBlue528, 117
lblLimeLime557, 117
lblCyanCyan586, 117
lblRedRed499, 152
lblFuchsiaFuchsia528, 152
lblYellowYellow557, 152
lblWhiteWhite586, 152

  1. With the pnlMain control selected, go to the toolbox and double-click the Label item.
  2. Set the Label control's properties as follows:

lblColor Control Properties
PropertyValue
NamelblColor
AutoSizeFalse
BackColorBlack
BorderStyleFixedSingle
Location499, 185
Size110, 45
TextNone

  1. With the pnlMain control selected, go to the toolbox and double-click the Label item.
  2. Set the Label control's properties as follows:

lblColourSelected Control Properties
PropertyValue
NamelblColorSelected
Location497, 27
TextColour: Black

Your application's user interface should now look like the illustration below.


The EasyPaint program interface with its colour selection controls

The EasyPaint program interface with its colour selection controls


Once we have coded the event handler for the colour selector controls, the user will be able to click on any one of the sixteen available selections to choose a colour, which will then be used by whatever drawing tool is chosen (including text). The large colour box underneath the colour selector controls will change to whatever the currently selected colour is, and the label above the colour selector controls will display the colour's name. The name of the colour selected will also be stored in a global program variable so that the various tools can be coded to output text or graphics in the correct colour.

The next task is to create controls to allow the user to select a drawing tool. The tool selection controls will again be labels, each of which will display an image, allowing the user to see at a glance what kind of tools are available. We have created a zipped file called easy_paint.zip that contains the images for the tool selection controls. The file can be downloaded from the link below.

Download the easy_paint.zip file here.

  1. If you haven't already done so, save your project to create the project directory.
  2. Download the tool selector control images using the link above and unzip them into your project directory.
  3. With the pnlMain control selected, go to the toolbox and double-click the Label item.
  4. Set the Label control's properties as shown below. To select an image for the Image property, click on the property's browse button, select the radio button labelled Project resource file:, click on the Import button, and browse to the file "brush.gif" (this should be located in your project folder).

lblBrush Control Properties
PropertyValue
NamelblBrush
AutoSizeFalse
BorderStyleFixed3D
Imagebrush.gif
Location499, 262
Size36, 36
TagBrush
TextNone

This control is the first of eleven tool selector controls. The remaining controls will share many of the same property values, with the only differences between them being the Name, Image, Location and Tag properties.

  1. Copy and paste the lblBrush control onto the picCanvas PictureBox control to create the rest of the tool selector controls (there will be ten more in all). Drag them approximately into position (see the illustration further down this page), then assign the properties specified below.

    Note: make sure the picCanvas control is selected before you paste each copy, or you will probably find it has been pasted behind the picCanvas control and you will not be able to drag it into position.

Tool Selector Control Properties
NameImageLocationTag
lblLineline.gif536, 262Line
lblTexttext.gif573, 262Text
lblSqOutsquare_outline.gif499, 302Square
lblSqFillsquare_filled.gif536, 302SquareFilled
lblRecOutrectangle_outline.gif573, 302Rect
lblRecFillrectangle_filled.gif499, 342RectFilled
lblCircOutcircle_outline.gif536, 342Circ
lblCircFillcircle_filled.gif573, 342CircFilled
lblEllOutellipse_outline.gif499, 382Ellipse
lblEllFillellipse_filled.gif536, 382EllipseFilled

  1. With the pnlMain control selected, go to the toolbox and double-click on the Label item.
  2. Set the Label control's properties as follows:

lblToolSelected Control Properties
PropertyValue
NamelblToolSelected
Location497, 243
TextTool: none selected

Your application's user interface should now look like the illustration below.


The EasyPaint program interface with its tool selection controls

The EasyPaint program interface with its tool selection controls


The remaining controls will allow the user to select the brush size (for freehand painting) and the pen width (for drawing lines and outline shapes) . Both controls consist of a combo box that allows the user to choose from a range of values (1 to 32 for the brush size, and 1 to 12 for the pen width).

  1. With the pnlMain control selected, go to the toolbox and double-click the Label item.
  2. Set the Label control's properties as follows:

lblBrushSize Control Properties
PropertyValue
NamelblBrushSize
AutoSizeFalse
Location496, 435
Size71, 13
TextBrush size:
TextAlignMiddleRight

  1. With the pnlMain control selected, go to the toolbox and double-click on the ComboBox item.
  2. Set the ComboBox control's properties as follows:

cmbBrushSize Control Properties
PropertyValue
NamecmbBrushSize
Items1-32*
Location573, 432
Size36, 23
SortedFalse
Text6

*In the Items property box, click on the browse button. You will see a dialog box like the one illustrated below. Type in the values (1-32), one per line, Then click OK.


The ComboBox control's String Collection Editor

The ComboBox control's String Collection Editor


  1. With the pnlMain control selected, go to the toolbox and double-click the Label item.
  2. Set the Label control's properties as follows:

lblPenWidth Control Properties
PropertyValue
NamelblPenWidth
AutoSizeFalse
Location496, 462
Size71, 13
TextPen Width:
TextAlignMiddleRight

  1. With the pnlMain control selected, go to the toolbox and double-click the ComboBox item.
  2. Set the ComboBox control's properties as follows:

cmbPenWidth Control Properties
PropertyValue
NamecmbPenWidth
Items1-12
Location573, 459
Size36, 23
SortedFalse
Text2

That's it for the interface design! We can now add the code. First, run the application to see what it looks like. You should see something very similar to the illustration below. Note that the controls (apart from the File menu) are disabled when the program first opens. That is as it should be.


The completed EasyPaint program interface

The completed EasyPaint program interface


  1. Switch to the code editor window and enter these declarations for module variables within the body of the frmEasyPaint class:

Dim G As Drawing.Graphics
Dim drawFlag As Boolean = False
Dim xDown, yDown, xUp, yUp As Integer
Dim intL, intR, intT, intB, intW, intH As Integer
Dim clrSelected As Color = Color.Black
Dim intToolSelected As Integer = 0
Dim intBrushSize As Integer = 6
Dim intPenWidth As Integer = 2
Dim intFontSize As Integer = 12
Dim strText As String
Dim strFont As String = "Arial"
Dim styFontStyle As FontStyle
Dim strFontStyleArray() As String = {"Regular", "Bold", "Italic", "Bold Italic", "Unknown", _
  "Unknown", "Unknown", "Unknown", "Regular Strikeout", "Bold Strikeout", _
    "Italic Strikeout", "Bold Italic Strikeout", "Regular Underline Strikeout", _
      "Bold Underline Strikeout", "Italic Underline Strikeout", _
        "Bold Italic Underline Strikeout"}
Dim bmpPic As Bitmap

Some explanations are required here. The first line of code declares a variable (G) of type Graphics. The Graphics class defines an incredible number of methods for drawing and manipulating gaphic objects. An object of this type inherits those methods, allowing us to call on them for the purposes of drawing graphics in our application. We can associate a control (for example a form or a picture box) with this variable, and by so doing effectively turn the control into a canvas on which to draw.

As you will see, we are going to create a bitmap image and draw graphics to it, which will make the task of saving our completed artwork to a file relatively easy. The next variable is a Boolean variable called drawFlag, which is set to True when the mouse is down and False when the mouse is up. Essentially, whatever drawing tool we are currently using can only have an effect when the mouse is down (i.e. the user is holding down the left mouse button).

The next four integer variables (xDown, yDown, xUp, and yUp) are used to track the screen positions (relative to the drawing surface) at which a mouse button is pressed down and released respectively. Note that for freehand drawing, we are interested in tracking the mouse in real time as it moves around the canvas. For all other controls, we require a snapshot of the points at which the mouse button is clicked and subsequently released.

The six integer variables declared on the next line (intL, intR, intT, intB, intW and intH) define the bounding rectangle for all of the geometric shapes we will draw (and also define the start and end point for lines). They represent the left and right x coordinates, the top and bottom y coordinates, and the width and height of the rectangle respectively.

The clrSelected variable has the type Color, which stores the RGB values for a specific colour (although many common colours are referenced using names like "Black", "Red", "Blue" etc.) This variable will be used to keep track of the colour currently selected by the user, and when the program starts is set to Color.Black (black) by default.

The next four variables are relatively self-explanatory. The integer variable intToolSelected tracks which drawing tool the user has selected at any given time (a value of 0 means no tool is selected). The intBrushSize and intPenWidth integer variables keep track of the brush size and pen width selected by the user, and intFontSize keeps track of the font size selected for drawing text.

The string variable strText will hold text entered by the user, which will be added to the image if the user clicks on the drawing surface while the text tool is selected. The strFont string variable holds the name of the selected font face, and is initially set to "Arial". The styFontStyle variable is a specialised numeric data type (FontStyle) that is used to enumerate the various style characteristics (e.g. Italic, bold, underline, strikethrough etc.) and combinations thereof.

The last module variable (strFontStyleArray()) is an array of type String that holds text descriptions of the various font styles and style combinations. It has displayed correct font information in the (admittedly limited) testing carried out on the application, although documentation relating to the various font styles and their enumerations has proved somewhat elusive (the information to hand has been gleaned through experimentation with various fonts).

  1. The New item in the File menu enables the user interface and allows the user to start selecting colours and using the drawing tools. In the form design window, click once on the File menu to expose the New menu item, then click once on New.
  2. In the Properties window, change the name of the control to mnuFileNew.
  3. Double-click on the New menu item to create its Click event handler, and enter the following code at the cursor:

pnlMain.Enabled = True
picCanvas.BackColor = Color.White
bmpPic = New Bitmap(picCanvas.Width, picCanvas.Height)
picCanvas.Image = bmpPic
G = Graphics.FromImage(bmpPic)
picCanvas.DrawToBitmap(bmpPic, picCanvas.ClientRectangle)

This relatively short piece of code activates the application by setting the Enabled property of the panel control (pnlMain) to True. It also sets the background colour of the picture box control to white, setting up a "fresh canvas" for the user's artistic endeavours. The third line of code creates a bitmap image (bmpPic) of the same size as the picture box, the fourth line assigns the bitmap image it to the Image property of picCanvas, and the fifth line of code links it with the graphics object (G), which means that any drawing operations invoked with G will draw directly to the bitmap image bmpPic.

The last line looks rather cryptic, but what it does is take the white rectangle that was drawn on picCanvas by the system when we set its background colour to white, and draw it into the the bitmap image (which is initially empty). That is why we elected not to display a border for our PictureBox control, since this would also have appeared in the bitmap image.

To see the effect of this code, run the application, open the File menu, and click on the New menu item. You should see something like the screenshot below. All of the controls are now enabled (although most of them will not do anything until we have added some more code).


The File>New menu item activates the interface controls

The FileNew menu item activates the interface controls


  1. Before we can do any drawing, we need to get the palette, tool, and font selection controls working. We will start with the palette selection controls. In the code editor, enter the following subroutine code in its entirety:

Private Sub lblPalette_Click(sender As Object, e As EventArgs) _
  Handles lblBlack.Click, lblNavy.Click, lblGreen.Click, lblTeal.Click, _
    lblMaroon.Click, lblPurple.Click, lblOlive.Click, lblSilver.Click, _
      lblGray.Click, lblBlue.Click, lblLime.Click, lblCyan.Click, _
        lblRed.Click, lblFuchsia.Click, lblYellow.Click, lblWhite.Click
          lblColor.BackColor = sender.BackColor
    clrSelected = sender.BackColor
  Select Case clrSelected
    Case Color.Black
      lblColorSelected.Text = "Colour: Black"
    Case Color.Navy
      lblColorSelected.Text = "Colour: Navy"
    Case Color.Green
      lblColorSelected.Text = "Colour: Green"
    Case Color.Teal
      lblColorSelected.Text = "Colour: Teal"
    Case Color.Maroon
      lblColorSelected.Text = "Colour: Maroon"
    Case Color.Purple
      lblColorSelected.Text = "Colour: Purple"
    Case Color.Olive
      lblColorSelected.Text = "Colour: Olive"
    Case Color.Silver
      lblColorSelected.Text = "Colour: Silver"
    Case Color.Gray
      lblColorSelected.Text = "Colour: Gray"
    Case Color.Blue
      lblColorSelected.Text = "Colour: Blue"
    Case Color.Lime
      lblColorSelected.Text = "Colour: Lime"
    Case Color.Cyan
      lblColorSelected.Text = "Colour: Cyan"
    Case Color.Red
      lblColorSelected.Text = "Colour: Red"
    Case Color.Fuchsia
      lblColorSelected.Text = "Colour: Fuchsia"
    Case Color.Yellow
      lblColorSelected.Text = "Colour: Yellow"
    Case Color.White
      lblColorSelected.Text = "Colour: White"
  End Select
End Sub

This event handler handles the Click event for all sixteen colour selection controls, and differentiates between them by looking at the BackColor property. The first line of code within the body of the event handler sets the background colour of the lblColor label control to the same colour as that of the colour selector control clicked by the user. The second line of code sets the value of the global variable clrSelected (which is of type Color) to that colour also.

The remainder of the code consists of a Select Case statement that tests to see which colour was selected by the user and changes the text displayed by the lblColorSelected control accordingly. To see the effect of this code, run the application, open the File menu, and click on the New menu item, then click on various colour selector controls.

The next task is to write the event handler for the drawing tool selection controls. As with the colour selector controls, a single event handler serves all of them.

  1. In the code editor, enter the following subroutine code in its entirety:

Private Sub lblTool_Click(sender As Object, e As EventArgs) _
  Handles lblBrush.Click, lblLine.Click, lblText.Click, lblSqOut.Click, _
    lblSqFill.Click, lblRecOut.Click, lblRecFill.Click, lblCircOut.Click, _
      lblCircFill.Click, lblEllOut.Click, lblEllFill.Click
  resetTools()
  Select Case sender.Tag
    Case "Brush"
      intToolSelected = 1
      lblBrush.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Brush"
    Case "Line"
      intToolSelected = 2
      lblLine.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Line"
    Case "Text"
      intToolSelected = 3
      lblText.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Text"
    Case "Square"
      intToolSelected = 4
      lblSqOut.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Square Outlined"
    Case "SquareFilled"
      intToolSelected = 5
      lblSqFill.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Square Filled"
    Case "Rect"
      intToolSelected = 6
      lblRecOut.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Rectangle Outlined"
    Case "RectFilled"
      intToolSelected = 7
      lblRecFill.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Rectangle Filled"
    Case "Circ"
      intToolSelected = 8
      lblCircOut.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Circle Outlined"
    Case "CircFilled"
      intToolSelected = 9
      lblCircFill.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Circle Filled"
    Case "Ellipse"
      intToolSelected = 10
      lblEllOut.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Ellipse Outlined"
    Case "EllipseFilled"
      intToolSelected = 11
      lblEllFill.BorderStyle = BorderStyle.FixedSingle
      lblToolSelected.Text = "Tool: Ellipse Filled"
  End Select
End Sub

  1. Note that the above subroutine calls another procedure called resetTools() which we have not yet created, and will therefore flag up an error. To rectify this, add the following code to the form:

Sub resetTools()
  lblBrush.BorderStyle = BorderStyle.Fixed3D
  lblLine.BorderStyle = BorderStyle.Fixed3D
  lblText.BorderStyle = BorderStyle.Fixed3D
  lblSqOut.BorderStyle = BorderStyle.Fixed3D
  lblSqFill.BorderStyle = BorderStyle.Fixed3D
  lblRecOut.BorderStyle = BorderStyle.Fixed3D
  lblRecFill.BorderStyle = BorderStyle.Fixed3D
  lblCircFill.BorderStyle = BorderStyle.Fixed3D
  lblCircFill.BorderStyle = BorderStyle.Fixed3D
  lblEllOut.BorderStyle = BorderStyle.Fixed3D
  lblEllFill.BorderStyle = BorderStyle.Fixed3D
End Sub

  1. We will now code the controls that allow the user to select the brush size and pen width. In the code editor, select the cmbBrushSize control using the drop-down list at the top centre of the code editor window, then select the SelectedValueChanged event using the drop-down list at the top right-hand side of the code editor window.
  2. Enter the following command at the cursor (this command is executed when the user selects a new value using the combo box and sets the module variable intBrushSize to the integer value represented by the selection):

intBrushSize = CInt(cmbBrushSize.Text)

  1. In the code editor, select the cmbPenWidth control using the drop-down list at the top centre of the code editor window, then select the SelectedValueChanged event using the drop-down list at the top right-hand side of the code editor window.
  2. Enter the following command at the cursor (this command is executed when the user selects a new value using the combo box and sets the module variable intPenWidth to the integer value represented by the selection):

intPenWidth = CInt(cmbPenWidth.Text)

  1. We will deal with the font selection next. In the form designer window, double click on the Select Font button and enter the following code at the cursor:

dlgFont.ShowDialog()
strFont = dlgFont.Font.Name
styFontStyle = dlgFont.Font.Style
intFontSize = dlgFont.Font.Size
lblFontDetails.Text = "(" & strFont & " " & intFontSize & "pt, " & strFontStyleArray(styFontStyle) & ")"

The above code opens the dlgFont dialog box to allow the user to select a font. Of the values returned by the dialog box, the font name (a string value) is assigned to the string variable strFontName, the style (an enumeration) is assigned to the style variable styFontStyle, and the font size (an integer value) is assigned to the integer variable intFontSize. All of these variables have module scope, and can be accessed by any procedure.

The information is then assembled into a string, which is assigned to the Text property of the control lblFontDetails, which informs the user which font face, font size and font style are currently selected. The text for the font style is selected from the string array strFontStyleArray() using the numeric styFontStyle value returned by the dialog box (you might recall we discussed the lack of documentation on this topic earlier).

  1. The next two procedures will enable us to actually do something useful with the application. In the code editor, select the picCanvas control using the drop-down list at the top centre of the code editor window, then select the MouseDown event using the drop-down list at the top right-hand side of the code editor window.
  2. Enter the code below at the cursor. This code sets the drawFlag variable to True to signal that drawing commands may be executed, and records the location of the cursor on the drawing surface at the moment when the mouse button is pressed.

drawFlag = True
xDown = e.X
yDown = e.Y

  1. In the code editor, select the picCanvas control again using the drop-down list at the top centre of the code editor window, then select the MouseMove event using the drop-down list at the top right-hand side of the code editor window.
  2. Enter the following code at the cursor.

If intToolSelected = 1 And drawFlag = True Then
  xDown = e.X
  yDown = e.Y
  G.FillEllipse (New SolidBrush (clrSelected) , xDown, yDown, intBrushSize, intBrushSize)
  picCanvas.Refresh()
End If

The second of these two procedures only applies to freehand drawing with the brush tool. Like the first procedure, it records the location of the cursor on the drawing surface at the moment when the mouse button is pressed, but because the procedure executes whenever the mouse is moved (providing the brush tool is selected and the drawFlag variable is set), then the mouse position is effectively tracked in real time.

As the mouse moves over the canvas, an ellipse is drawn at each new position recorded to create the impression of a paintbrush moving across the drawing surface. The FillEllipse method of the Graphics object uses the values stored in the clrSelected and intBrushSize variables to determine the colour and size respectively of the ellipse drawn (which is in fact a circle because it uses intBrushSize for both dimensions of the ellipse), and the xDown and yDown variables to determine where to draw each ellipse as the mouse moves.

Run the application, select New from the File menu and try out the brush tool to see how it works. Note that we have not yet written any code for the MouseUp event, so you will find that the only way to stop painting with the brush is to select another tool!

The MouseUp event does most of the useful work of the application in that it draws shapes, lines and text strings on the drawing surface. Before we write the MousUp event handler however, we need to consider the behaviour associated with the various shape tools (the tools that draw squares, rectangles, circles and ellipses) and how we want to implement them, since they will each require a slightly different approach.

Visual Basic draws shapes within a bounding rectangle defined by its top left hand corner, width and height. The top left-hand corner is defined as a pixel offset from the top left-hand corner of the drawing surface (in our case, this is the PictureBox object picCanvas). The width and height are also defined using pixel values. We will create subroutines to set up the coordinates for the rectangle within which the different shapes will be drawn, starting with the square.

  1. Somewhere within the form's class definition, insert the following subroutine:

Sub dimSquare()
  If xUp < 0 Then xUp = 0
  If xUp > 480 Then xUp = 480
  If yUp < 0 Then yUp = 0
  If yUp > 480 Then yUp = 480
  intW = Math.Abs(xUp - xDown)
  intH = Math.Abs(yUp - yDown)
  If intW > intH Then intW = intH
  If xUp < xDown Then intL = xDown - intW Else intL = xDown
  If yUp < yDown Then intT = yDown - intW Else intT = yDown
End Sub

The first four lines here are concerned with restricting the xUp and yUp coordinates to a point within the bounding rectangle of the drawing surface. The next two lines call the Math.Abs() function to set absolute values of intW and intH respectively (saving us the bother of having to work out whether to subtract xUp from xDown, or the other way round, and so on).

The next line of code determines how big the square will be when it is drawn. Since it is a square, then by definition the sides will be equal in length. We will (somewhat arbitrarily) limit the width and height to whichever is the shorter of intW and intH. The square will be drawn within a bounding rectangle (or bounding square, in this case) that extends left or right, up or down from the xDown and yDown coordinates, depending on whether the user drags the mouse left or right, up or down, after they have pressed the mouse button.

The last two lines of code determine where the top left hand corner of the bounding rectangle should be (this will have the x and y coordinates intL and intT respectively).

  1. The rectangle shape differs from the square slightly in that its width and height can have different values, but the subroutine to find the bounding rectangle is very similar to that for the square. Somewhere within the form's class definition, insert the following subroutine:

Sub dimRectangle()
  If xUp < 0 Then xUp = 0
  If xUp > 480 Then xUp = 480
  If yUp < 0 Then yUp = 0
  If yUp > 480 Then yUp = 480
  intW = Math.Abs (xUp - xDown)
  intH = Math.Abs (yUp - yDown)
  If xUp < xDown Then intL = xDown - intW Else intL = xDown
  If yUp < yDown Then intT = yDown - intH Else intT = yDown
End Sub

The first six lines of code are identical to the previous procedure. The only real differences are that the line of code that reduces the value of intW to that of intH (if the latter is smaller) in the dimSquare() procedure has been removed, and that the final line of code uses intH instead of intW to determine the y coordinate for the top of the bounding rectangle.

  1. For the circle shape, we have decided (another arbitrary decision!) to use the xDown and yDown coordinates as the centre of the circle, and the value of intW or intH (whichever is greater) to determine the circle's diameter. Somewhere within the form's class definition, insert the following subroutine:

Sub dimCircle()
  If xUp < 0 Then xUp = 0
  If xUp > 480 Then xUp = 480
  If yUp < 0 Then yUp = 0
  If yUp > 480 Then yUp = 480
  intW = Math.Abs (xUp - xDown)
  intH = Math.Abs (yUp - yDown)
  If intW < intH Then intW = intH
  intL = xDown - intW
  intT = yDown - intW
  intW *= 2
End Sub

The first six lines of the subroutine are identical to those used in the subroutines used for the square and the rectangle (advocates of re-usable code will be straining at the leash to remove this code to a separate subroutine - feel free to optimise the code!).

We are only going to use one of the dimensions (intW) to draw the circle, so the next line of code checks to see if intW is smaller than intH. If so the value of intW is increased to match the value of intH. This dimension represents the circle's radius, so the coordinates for the top left-hand corner of the bounding rectangle will be intW pixels to the left of the point defined by the xDown and yDown coordinates, and the same distance above it.

The penultimate two lines of code subtract intW from xDown and yDown to find intL and intY respectively. The last line of code doubles the value of intW, so that the bounding rectangle (again, read bounding square) for the circle extends for the same distance on either side, and above and below, the point defined as the centre of the circle (xDown, yDown).

  1. The ellipse shape differs from the circle only in that its width and height can have different values, so the subroutine to find the bounding rectangle is very similar to that for the circle. Somewhere within the form's class definition, insert the following subroutine:

Sub dimEllipse()
  If xUp < 0 Then xUp = 0
  If xUp > 480 Then xUp = 480
  If yUp < 0 Then yUp = 0
  If yUp > 480 Then yUp = 480
  intW = Math.Abs (xUp - xDown)
  intH = Math.Abs (yUp - yDown)
  intL = xDown - intW
  intT = yDown - intH
  intW *= 2
  intH *= 2
End Sub

The only real difference between dimCircle() and dimEllipse() are that dimEllipse() uses the values of both the intW and intH variables to determine the position of the top left-hand corner of the bounding rectangle and its size. Note that the dimensioning of shapes works the same regardless of whether the shapes are outlined or filled. The real difference between the two types of shape is in the methods used to draw them by the Graphics object.

The next piece of code we need to write is the MouseUp event handler for the picCanvas control, which will enable us to demonstrate the various shape tool methods.

  1. In the code editor, select the picCanvas control using the drop-down list at the top centre of the code editor window, then select the MouseUp event using the drop-down list at the top right-hand side of the code editor window.
  2. Enter the following code at the cursor:

Dim brushFill As SolidBrush = New Drawing.SolidBrush(clrSelected)
Dim penLine As New Pen(clrSelected, intPenWidth)

drawFlag = False
xUp = e.X
yUp = e.Y

Select Case intToolSelected
  Case 2
    G.DrawLine(penLine, xDown, yDown, xUp, yUp)
  Case 3
    strText = txtInsertText.Text
    G.DrawString(strText, New System.Drawing.Font(strFont, intFontSize, styFontStyle), _
    brushFill, xUp, yUp)
  Case 4
    dimSquare()
    G.DrawRectangle(penLine, intL, intT, intW, intW)
  Case 5
    dimSquare()
    G.FillRectangle(brushFill, intL, intT, intW, intW)
  Case 6
    dimRectangle()
    G.DrawRectangle(penLine, intL, intT, intW, intH)
  Case 7
    dimRectangle()
    G.FillRectangle(brushFill, intL, intT, intW, intH)
  Case 8
    dimCircle()
    G.DrawEllipse(penLine, intL, intT, intW, intW)
  Case 9
    dimCircle()
    G.FillEllipse(brushFill, intL, intT, intW, intW)
  Case 10
    dimEllipse()
    G.DrawEllipse(penLine, intL, intT, intW, intH)
  Case 11
    dimEllipse()
    G.FillEllipse(brushFill, intL, intT, intW, intH)
End Select
picCanvas.Refresh()

Here we see the tools used to draw shapes - the SolidBrush and the Pen. The SolidBrush is used to draw the filled shapes, while the Pen is used to draw lines and outline shapes. Both accept a Color argument, which is set to clrSelected at the beginning of the procedure. The Pen method also accepts a Width argument that determines the thickness of lines or the borders of any outline shapes drawn.

In our MouseUp() procedure, we use the value of intPenWidth. Although they have default values, both clrSelected and intPenWidth can be set by the user at run time. The next three lines of code set the drawFlag variable to False (signalling that no drawing tools are currently active), and set the xUp and yUp variables to the x and y coordinates (relative to the drawing surface) of the point at which the mouse button was released. The remaining code mostly consists of a Select Case statement that checks to see if a drawing tool is currently selected, and if so takes the appropriate action.

If the Line tool has been selected, the Drawline() method of the Graphics object draws the line with the penLine object (which, if you remember, has been set to the currently selected colour and line width) between the coordinates defined by xDown, yDown, and xUp, yUp.

If the Text tool is selected, whatever text is currently in the txtInsertText control's Text property (if any) is drawn using the font face, font style, and font size selected by the user, starting at the position defined by xUp, yUp.

If a shape tool is selected then - depending on which shape tool has been chosen - the appropriate subroutine (dimSquare(), dimRectangle(), dimCircle() or dimEllipse()) will be called to set the bounding rectangle.

For outline squares or rectangles, the Graphic object's DrawRectangle() method is used with the penLine object, the only difference being that the width and height dimensions used for drawing a square will be identical, whereas those used for drawing a rectangle will (usually) be different. For solid squares or rectangles, the method used will be FillRectangle() and the object used to draw will be brushFill.

Circles and ellipses follow a similar patter, with the DrawEllipse() method used for the outline shapes and FillEllipse() used for their solid counterparts.

The final line of code calls the Graphic object's Refresh() method to make sure the changes made by the program are displayed to the screen immediately.

  1. The last task we must undertake is to finalise the File menu items and write the code that makes them do something. The Save item in the File menu enables the user to save their artwork to a file. In the form design window, click once on the File menu to expose the Save menu item, then click once on Save.
  2. In the Properties window, change the name of the control to mnuFileSave.
  3. Double-click on the Save menu item to create its Click event handler, and enter the following code at the cursor:

sfdSavePic.Filter = "Bitmap |*.bmp"
If sfdSavePic.ShowDialog = DialogResult.OK Then
picCanvas.Image.Save(sfdSavePic.FileName, Drawing.Imaging.ImageFormat.Bmp)
MsgBox("File saved.")
End If


This code uses the sfdSavePic SaveFileDialog object that we added to the form earlier, and sets up a filter that will only allow the user to choose the bitmap file format. It will also add the ".bmp" file extension to any files saved. What is actually being saved here is the picCanvas PictureBox control's Image, which is a bitmap object, and to which all our graphics commands draw. To complete the application, there is one more very trivial task:

  1. In the form design window, click once on the File menu to expose the Exit menu item, then click once on Exit.
  2. In the Properties window, change the name of the control to mnuFileExit.
  3. Double-click on the Exit menu item to create its Click event handler, and enter the following command at the cursor:

End


The completed program in action

The completed program in action