Creating Your Own Drawing Application with Visual Basic .NET, Part 4

If you have been following this series from the beginning, you have probably wondered what more can we do? Well, there’s always something more to do in any program. Sometimes, it is something that needs fixing, sometimes a necessary update; in my case, it is just plain curiosity. Part 4 contains the following changes and updates:

Updates

  • I added a Preview form
  • I added Selection Tools
  • I added Cropping functionality
  • I added Zooming capabilities
  • Rotation abilities
  • Eyedropper
  • Drawing Text
  • Changing the canvas background colour

Fixes

  • Changed logic behind the Round rectangle creation code. The reason for this is mainly the fact that exceptions were caused when clicking and not dragging while this tool is selected.
  • Added some logic to prevent exceptions when attempting to fill an object with gradients (which at this stage is sadly still not yet included in the program).

If you haven’t been following this article series since the beginning, make sure to visit each article here:

Part 1, Part 2, Part 3.

Adding the Preview Form, and Why

Your preview form will allow you to see the current selection more properly; especially when zooming, or rotating, this is handy because you can only work with a certain part of your image, and not affect the rest of the picture.

Design

To add the Preview form, follow these simple steps

  1. From the Visual Basic IDE, select Project, Add Windows Form…
  2. Name the form frmPreview.
  3. Set frmPreview’s AutoScroll property to True; this will produce scrollbars on your form so that you can scroll a large image.
  4. Set frmPreview’s FormBorderStyle property to SizableToolWindow.
  5. Set frmPreview’s BackColor to White.
  6. Set the ShowInTaskbar property to False, so that this form’s title doesn’t show on the Taskbar.
  7. Set the Text property to Preview.
  8. Add a Picturebox named picPreview to this new form.
  9. Set picPreview’s BackColor property to White.
  10. Set picPreview’s Width to 900—to make sure your selections don’t get cropped.
  11. Set picPreview’s Height to 500.

Your Preview form is now created.

Coding

Because this form will be displayed all the time, and used from different forms, you need to create a Public object, representing frmPreview. Open the Module (modCanvas.vb) and add the following code:

    Public FPreview As New frmPreview    'frmPreview Object

The first place you use your newly created form, is of course frmMDICanvas. Add the following lines in the Form_Load event of frmMDICanvas:

   FPreview.MdiParent = Me                 'Preview = Child Form
   FPreview.Show()
   FPreview.Location  = New Point(1, 1)    'Set Preview Location

If you were to build and run your project, you would see frmPreview alongwith the other two forms, frmCanvas and frmTools.

Adding Selection Capabilities

Every graphics program has a means of selecting parts of images. After selecting certain parts of a image, further editing can be done with it. What follows now is the introduction of creating selection tools for these types of programs.

Adding the Necessary Controls and Variables

Design

  1. Open frmTools and add a new Button, named btnCSelect.
  2. Set the FlatStyle property to Popup, so that it looks & acts the same as your other buttons on frmTools.
  3. Set the Width to 75.
  4. Set the Height to 23.
  5. You also could add an Image to the button, or just use the Text property to explain its purpose to the user. In the attached sample, I made use of a piture named Select.png.

Coding

Following the same principle as with all parts of this article series, you must add a Public Boolean flag to determine whether or not this tool has been selected. To do this, open modCanvas and add the following:

Public blnSelNormal As Boolean    'Is The Selection Tool Clicked?

In all of the following events, you need to set this flag to False, just to ensure that you are always dealing only with the appropriate tool:

butCCircle_Click
butCSquare_Click
butCPencil_Click
butCEraser_Click
frmTools_Load
mnuCOutline_Click
mnuCFillDraw_Click
mnuCDrawFill_Click
mnuTriangle_Click
mnuHeart_Click
mnuRoundRect_Click
mnuLine_Click
mnuSpiral_Click

blnSelNormal = False

In frmTools_Load, you also need to add a Tooltip for your new button:

 tipCanvas.SetToolTip(btnCSelect, "Select")

Create a Click event for the btnCSelect button, and the following into it:

Private Sub btnCSelect_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnCSelect.Click

   'All Tools, Except For The Current One, Are Not Selected
   blnCircleClicked    = False
   blnSquareClicked    = False
   blnDrawClicked      = False
   blnEraserClicked    = False
   blnTriangleClicked  = False

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = False

   blnCropClicked      = False
   blnRotateClicked    = False
   blnZoomScroll       = False
   blnSelNormal        = True
   blnEyeDropClicked   = False
   blnTextClicked      = False
End Sub

When this button is selected, all the other tools become deselected. Now, you are finally ready to actually start using the Selection button.

Adding a Moveable Selection Rectangle

Yes, that’s right. You are going to create a Selection rectangle that can be moved around the canvas, and it will keep on updating the Preview form, based on the new selection. To achieve this, you need to declare variables to keep track of current selection coordinates, current selection size, and a boolean flag to determine whether or not you are moving the selection box.

Open frmCanvas, and add these variables in General declarations:

Private blnSelMouseDown As Boolean          'Is The Mouse Down B?
Private ptSelMouseDown, ptSelMouseNow, _    'Selection Rect Variables
   ptSelMouseLast, ptSelMouseUp As Point
Private pSelPen As New Pen(Color.Black)     'Selection Pen
Private rtSel As Rectangle                  'Selection Rectangle
Private offYSel As Integer                  'Selection Y
Private offXSel As Integer                  'Selection Y
'Are We Moving The Selection Rectangle B?
Private blnMovingSelectionRect As Boolean

The next step is to create a function that creates your selection rectangle:

'Return The Rectangle That Goes Through The Two Points Passed To It
Private Function CanvasSelRectangle(ByVal Point1 As Point, _
   ByVal Point2 As Point, _
   Optional ByVal ByValInflate As Integer = 0) As Rectangle
   Dim rect As Rectangle    'Declare Rect Variable
   Dim Left, Top, Right, Bottom As Integer    'Selection Sides

   If Point1.X < Point2.X Then
      Left  = Point1.X
      Right = Point2.X
   Else
      Left  = Point2.X
      Right = Point1.X
   End If

   If Point1.Y < Point2.Y Then
      Top    = Point1.Y
      Bottom = Point2.Y
   Else
      Top    = Point2.Y
      Bottom = Point1.Y
   End If

   If Left = Right  Then Right  += 1
   If Top  = Bottom Then Bottom += 1

   'Left, Top, Right, Bottom
   rect = Rectangle.FromLTRB(Left, Top, Right, Bottom)
   rect.Inflate(ByValInflate, ByValInflate)
   Return rect
End Function

You need to create a new sub procedure to transfer the contents of your selection rectangle (in other words, the image) to frmPreview:

'Transfer Contents Of Selection Rect To The Viewer
Private Sub CanvasUpdateViewer()
   'Ensure All Left Top Properties Are OK
   '(Where Picture Starts To Draw And End )
   'Create Bitmap
   Dim bmpImage As New Bitmap(rtSel.Width, rtSel.Height)
   'Create Graphics Object
   Dim gImage As Graphics = Graphics.FromImage(bmpImage)

   If blnSelNormal Then    'If Selecting
       'Set Interpolation Mode
       'Interpolation, Basically Means The Quality Of The Rendered
       'Image
       'Bicubic Is The Slowest Interpolation Option, But, It's The
       'Most Precise, And Has The Best Quality
       gImage.InterpolationMode =
          InterpolationMode.HighQualityBicubic
       'Draw Current Selection Picture
       gImage.DrawImage(picCDraw.Image, New Rectangle(0, 0, _
          rtSel.Width, rtSel.Height), rtSel, GraphicsUnit.Pixel)
       FPreview.picPreview.Image = bmpImage    'Display Picture
   End If

Making It Work

In frmCanvas_Load, initialise your Selection pen and selection rectangle:

rtSel = New Rectangle(0, 0, 0, 0)
pSelPen.DashStyle = DashStyle.Dot    'Set Selection Pen Style

The real work needs to be done in the three Mouse events:

  1. MouseDown
  2. MouseMove
  3. MouseUp

The most important Mouse event among the three is probably the MouseDown event. I say This because this is where you need to determine whether you are just drawing the selection rectangle, or are you moving the already drawn selection? Take a closer look:

'If Selection Rect Is Clicked On, Enter Drag Mode
If rtSel.Contains(e.X, e.Y) And e.Y <> ptSelMouseDown.Y And _
   e.X <> ptSelMouseDown.X Then
   blnMovingSelectionRect = True
   offXSel = e.X - rtSel.X
   offYSel = e.Y - rtSel.Y
Else    'exit drag mode
   blnMovingSelectionRect = False
   picCDraw.Invalidate()
   ptSelMouseDown = New Point(e.X, e.Y)
   ptSelMouseLast = ptSelMouseDown
End If

Here, you use the Contains method of your rectangle to establish whether you have clicked inside of it. The test:

If rtSel.Contains(e.X, e.Y) And e.Y <> ptSelMouseDown.Y _
   And e.X <> ptSelMouseDown.X Then

tests for the current mouse X and Y coordinates. If the selection rectangle contains the location you clicked on, it does a further test to make sure that the X and Y is not the same as the starting coordinates of the rectangle. If all of these tests succeed, you are in “moving” mode. If any of these tests fail, you are in “drawing” mode.

In the MouseMove event, you need to establish the current mode you are in. If you are in “moving” mode, you need to update not only the selection rectangle coordinates, but also the image to be displayed on frmPreview as well. If you are in “drawing” mode, you simply need to draw the rectangle according to the user’s mouse movements.

If blnMovingSelectionRect Then    'Move The Selection
   'move the selection rectangle
   picCDraw.Invalidate(CanvasSelRectangle(ptSelMouseDown, _
                                          ptSelMouseLast))
   picCDraw.Refresh()
   Dim r As Rectangle = New Rectangle(e.X - offXSel, _
                                      e.Y - offYSel, _
                                      rtSel.Width, rtSel.Height)
   picCDraw.CreateGraphics.DrawRectangle(pSelPen, r)
   rtSel = r

   CanvasUpdateViewer()
Else

   'Draw Selection Box As We Drag
   picCDraw.Invalidate(CanvasSelRectangle(ptSelMouseDown, _
                       ptSelMouseLast, 2))
   picCDraw.Refresh()
   picCDraw.CreateGraphics.DrawRectangle(pSelPen, _
      CanvasSelRectangle(ptSelMouseDown, ptSelMouseNow))
   ptSelMouseLast = ptSelMouseNow
End If

Your MouseUp event only needs to fire when you are in “drawing” mode. Basically, all you need to do here is to obtain the ending point coordinates so that you can finish the drawing of the selection rectangle. You also filled the selection rectangle with the WhiteSmoke colour, which ends up displaying a lighter shade (of whatever colour was selected), so that you can see the selection more clearly:

'If We Are Not Moving The Selection Rectangle, Just Draw It With
'Coords
If Not blnMovingSelectionRect Then

   ptSelMouseUp = New Point(e.X, e.Y)

   rtSel = (CanvasSelRectangle(ptSelMouseDown, ptSelMouseUp))
   'Fill With A Smokey White, Ends Up Giving A Lighter Shade Of
   'Current Colour
   'draw a rectangle using solid brush white smoke with
   'alphablending
   picCDraw.CreateGraphics.FillRectangle(New SolidBrush _
      (Color.FromArgb(50, 245, 245, 245)), _
      CanvasSelRectangle(ptSelMouseDown, ptSelMouseNow))

   CanvasUpdateViewer()
End If

More by Author

Must Read