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

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

Cropping, Zooming, and Rotating

Before I can actually start explaining these concepts, you need to follow the same old procedure and add a couple of controls to frmTools:

Control Name Property Setting
Button butCCrop Name butCCrop
    BackColor White
    FlatStyle Flat
    Image Image of your choice
    Size 32, 32
Button butCRotate Name butCRotate
    BackColor White
    FlatStyle Flat
    Image Image of your choice
    Size 32, 32
Vertical Scrollbar vsCZoom Name vsCZoom
    LargeChange 100
    Maximum 2000
    Minimum 0
    SmallChange 50

The next step, of course, is to set your Boolean flags appropriately. Declare three Boolean variables in modCanvas, and name them blnCropClicked, blnRotateClicked, and blnZoomScroll respectively. You must remember that you still need to set all these flags to False in every other event, and True only once the particular tool has been selected.I'll leave that part up to you now.

Cropping

The cropping logic I've used is exactly the same as the selection logic. You created a bitmap based on the selection rectanangle's parameters, and then drew it onto frmPreview. The main aspect of cropping (in respect to this project) comes with the CanvasSelRectangle function; it returns your cropped rectangle, with the specified coordinates:

'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

Rotating

Usually in most drawing/graphics applications, there is a way to rotate an image or any part of it. Ways of rotating usually includes Clockwise rotation, where the image gets rotated in 90 degree increments; and counter-clockwise rotation, where the opposite happens—the image gets rotated in 90 degree decrements. You can include rotation through the use of the Image's RotateFlip method. This method includes options for rotating the image 90, 180, and 270 degrees respectively. Flipping is also covered with this method.

Rotating the image 90 degrees every time for clockwise rotation, and 270 for counter-clockwise rotation would suffice here. It makes sense, because calling:

Image.RotateFlip(RotateFlipType.Rotate90FlipNone

or

Image.RotateFlip(RotateFlipType.Rotate270FlipNone)

every time we want to rotate left or right is all you would need to do.

Seeing the fact that this article is supposed to show you how to rotate graphics, I will include options to rotate 90, 180, and 270 degrees. The RotateFlip method doesn't include an option to rotate 360 degrees.

To rotate, my way, I created a Counter variable that increments or decrements based on the particular selection. Based on this variable's value, I do the rotation:

'Create Rotation Graphic
Dim bmpRotateImage As New Bitmap(rtSel.Width, rtSel.Height)
Dim gRotateImage As Graphics = Graphics.FromImage(bmpRotateImage)

Select Case RotateCounter    'Determine Rotation Angle
   Case 90
      gRotateImage.DrawImage(picCDraw.Image, New Rectangle(0, 0, _
         rtSel.Width, rtSel.Height), rtSel, GraphicsUnit.Pixel)
      FPreview.picPreview.Image = bmpRotateImage
      'Rotate 90 Degrees
      FPreview.picPreview.Image.RotateFlip(RotateFlipType._
                                           Rotate90FlipNone)
   Case 180
      gRotateImage.DrawImage(picCDraw.Image, New Rectangle(0, 0, _
         rtSel.Width, rtSel.Height), rtSel, GraphicsUnit.Pixel)
      FPreview.picPreview.Image = bmpRotateImage
      'Rotate 180 Degrees
      FPreview.picPreview.Image.RotateFlip(RotateFlipType. _
                                           Rotate180FlipNone)
   Case 270
      gRotateImage.DrawImage(picCDraw.Image, New Rectangle(0, 0, _
         rtSel.Width, rtSel.Height), rtSel, GraphicsUnit.Pixel)
      FPreview.picPreview.Image = bmpRotateImage
      'Rotate 270 Degrees
      FPreview.picPreview.Image.RotateFlip(RotateFlipType. _
                                           Rotate270FlipNone)
   Case 360
      gRotateImage.DrawImage(picCDraw.Image, New Rectangle(0, 0, _
         rtSel.Width, rtSel.Height), rtSel, GraphicsUnit.Pixel)
      FPreview.picPreview.Image = bmpRotateImage
      'Start Over
      FPreview.picPreview.Image.RotateFlip(RotateFlipType. _
                                           Rotate90FlipNone)
   Case Is > 360    'Start Over Clockwise
      RotateCounter = 90
   Case Is < 0      'Start Over Counter Clockwise
      RotateCounter = 360
End Select

First, I declared a created a Bitmap object, based on the current coordinates of the selection rectangle. Then, I drew the selection onto the Graphics object. Finally, I did the rotation, and displayed the resulting bitmap onto frmPreview.

Custom Rotation

As you can see, the RotateFlip method is quite limited; at least, that's my opinion. I would have liked that options for a custom rotation value would be included. To rotate a graphic based on a custom value, you need to include your own logic. Let me explain:

A rectangle has four corners, or four points. That explains the complexities of a rectangular shape. You are dealing with a selection rectangle here. Now, whatever part of the image is selected will be part of this rectangle, so the first thing you would need to do is to get the four points (corners of the selection):

'Get The Width Of Selection
Dim sngWidth As Single  = bmpRotateImage.Width
'Get The Height Of Selection
Dim sngHeight As Single = bmpRotateImage.Height

Dim pCustCorners As Point() = { _
   New Point(0, 0), _
   New Point(sngWidth, 0), _
   New Point(0, sngHeight), _
   'Create The Points For Each Corner Of The Selection
   New Point(sngWidth, sngHeight)}

Here, you get the Height and Width of your rectangle. Then, you get the coordinates of each corner of the recatngle.

  • The first point is located at 0, 0—meaning the Top left corner of the rectangle
  • The second point is located at the width of the rectangle, and 0—the Top right corner
  • The third point is located at 0, and the height of the rectangle—Bottom left
  • The last point is located at the width of the rectangle, the height of the rectangle—Bottom right

Now, think logically for a moment. When rotating something, there is always one point whose coordinates doesn't actually change; in this case, the Top left corner will not change. So, in other words, you are only going to change the X and Y coordinates of the second, third, and fourth corners. Because you are using angles, you are going to need your three mathematical friends: PI, Sin, and Cos. So, remember to include the System.Math namespace.

gRotateImage.DrawImage(picCDraw.Image, New Rectangle(0, 0, _
   rtSel.Width, rtSel.Height), rtSel, GraphicsUnit.Pixel)
FPreview.picPreview.Image = bmpRotateImage

'Get The Width Of Selection
Dim sngWidth As Single  = bmpRotateImage.Width
'Get The Height Of Selection
Dim sngHeight As Single = bmpRotateImage.Height

'Create The Points For Each Corner Of The Selection
Dim pCustCorners As Point() = { _
   New Point(0, 0), _
   New Point(sngWidth, 0), _
   New Point(0, sngHeight), _
   New Point(sngWidth, sngHeight)}

Dim CustCX As Single  = sngWidth / 2       'Middle X
Dim CustCY As Single = sngHeight / 2       'Middle Y

Dim RotateLoop As Long                     'Rotation Loop

For RotateLoop = 0 To 3                    'Loop Through Corners
   pCustCorners(RotateLoop).X -= CustCX    'Set X
   pCustCorners(RotateLoop).Y -= CustCY    'Set Y
Next RotateLoop

'Angle Of Rotation, In Radians
Dim RotateTheta As Single = CustomRotateVal * PI / 180.0
Dim RotateSin As Single   = Sin(RotateTheta)    'Sine Of Angle
Dim RotateCos As Single   = Cos(RotateTheta)    'Cosine Of Angle

Dim rX As Single
Dim rY As Single

For RotateLoop = 0 To 3                       'Do The Rotation
   rX = pCustCorners(RotateLoop).X
   rY = pCustCorners(RotateLoop).Y
   pCustCorners(RotateLoop).X = rX  * RotateCos + rY * RotateSin
   pCustCorners(RotateLoop).Y = -rX * RotateSin + rY * RotateCos
Next RotateLoop

Dim rotXmin As Single = pCustCorners(0).X
Dim rotYmin As Single = pCustCorners(0).Y

For RotateLoop = 1 To 3    'Loop Through Corners 2,3,4
   If rotXmin > pCustCorners(RotateLoop).X _
      Then rotXmin = pCustCorners(RotateLoop).X
   If rotYmin > pCustCorners(RotateLoop).Y  _
      Then rotYmin = pCustCorners(RotateLoop).Y
Next RotateLoop

For RotateLoop = 0 To 3
   pCustCorners(RotateLoop).X -= rotXmin
   pCustCorners(RotateLoop).Y -= rotYmin
Next RotateLoop

'X & Y Of New Bitmap
Dim bmpCustRotateDest As New Bitmap(CInt(-2 * rotXmin), _
                                    CInt(-2 * rotYmin))

Dim gCustRotateDest As Graphics =
   Graphics.FromImage(bmpCustRotateDest)
ReDim Preserve pCustCorners(2)
'Draw New Image In Memory
gCustRotateDest.DrawImage(bmpRotateImage, pCustCorners)
'Display Rotated Graphic
FPreview.picPreview.Image = bmpCustRotateDest

In the Canvas project, I made use of a Context Menu object on the Rotation button. This menu included options to rotate clockwise, counter-clockwise, and this custom rotation. With custom rotation, I also included a new form named frmCustRotate. After you have chosen an option on how to rotate, select the area you want to be rotated.

Zooming

There aren't many heartaches in zooming pictures. I'll ask you to think about what actually happens when a picture gets zoomed. You'll agree with me when I say that the size of the particular picture changes according to the zoom specification. What I did here was to save the original picture in a bitmap variable; then, based on the zoom specification, I multiplied the height and width with the value of the scrollbar, THEN, finally divided it by 100. I divided this by 100 because of the fact that if you multiply a Width value of 200, with a zoom value of 200, I will get a width of 40,000! By dividing 40,000 by 100, I get 400.

Dim OriginalImage As Image

OriginalImage = picCDraw.Image    'Get Original (First) Image
'Zoom
Dim bmpZoomImage As New Bitmap(OriginalImage, _
   (Convert.ToInt32(bmpImage.Width  * ScrollVal) / 200), _
   (Convert.ToInt32(bmpImage.Height * ScrollVal  / 200)))
Dim gZoomImage As Graphics = Graphics.FromImage(bmpZoomImage)

gZoomImage.InterpolationMode = InterpolationMode.HighQualityBicubic

FPreview.picPreview.Image = bmpZoomImage    'Display The New Graphic

In the Canvas project, I've made use of a Vertical scrollbar to zoom smaller and bigger; after you have decided upon a zoom specifcation, use the select button again, to display the bigger/smaller image in frmPreview.

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

Using the Eyedropper

Eyedroppers usually are used to capture a certain color value from the drawing. Having this functionality makes life a bit easier, especially if you have used a certain colour combination that is not readily available in the color selection box. To get a specific colour value at a specific point, you need to use the Bitmap's GetPixel method in the Mousedown event:

'Get Current Pixel Colour
cColor = bImage.GetPixel(e.X, e.Y)

Can't get much easier than that! The above statement obtains the colour value according to the Mouse's X and Y coordinates (where the button was pressed down).

Drawing Text

The Graphics object includes a method named DrawString. Basically, what this method needs to work is:

  • The String to Draw
  • The Font to be used
  • The colour of the text. This makes use of a Brush object's colour, meaning you could actually use any type of brush here, but, I decided to use a SolidBrush for now
  • The X coordinate
  • The Y coordinate
If blnTextClicked Then     'Is The Text Tool Clicked?
   Dim sbTextBrush As New SolidBrush(cColor)    'Color Of Text
   Dim TextX As Integer    'X
   Dim TextY As Integer    'Y

   TextX = e.X
   TextY = e.Y

   'Draw String Entered With Selected Font
   gCanvas.DrawString(strCanvasText, CanvasFont, sbTextBrush, _
                      TextX, TextY)
   picCDraw.Refresh()
End If

In the Canvas project, I made use of an Inputbox to obtain the text you want to draw, and the Font Dialog for the font the text must be drawn in. After you have chosen a font and entered the text you want drawn, you can just click anywhere on the canvas to draw the text onscreen.

Fixes

Fixing the Rounded Rectangle

This line:

If RectRadius > Rectwidth / 2 OrElse RectRadius > RectHeight / 2 _
   Then RectRadius = RectHeight / 2

Caused an error if only clicked once, instead of dragging. So, to save me more humiliation, I commented both occurences of it in the MouseUp event. My whole Rounded Rectangle creation code now looks like the following:

'Determine If The RoundRect Tool Has Been Clicked
If blnRoundRectClicked Then

   'Yes, It Has Been Clicked, Set The Pen's Color To Selected Color
   pRoundRectPen.Color = cColor

   If blnOutlineDrawClicked Then

      'Draw The RoundRect With The Current Starting And Ending Values
      Dim RectX      As Integer = Math.Min(sStartX, sEndX)
      Dim RectY      As Integer = Math.Min(sStartY, sEndY)
      Dim RectWidth  As Integer = Math.Abs(sStartX - sEndX)
      Dim RectHeight As Integer = Math.Abs(sStartY - sEndY)

      Dim RectRadius As Integer = 15
      Dim pRRPath As New System.Drawing.Drawing2D.GraphicsPath

      'Part 4
      'Removed, Caused Error If Only Clicked Once, Instead Of
       'Dragging
      'Now, This Gives A Sniper Type CrossHair When Clicked Once
      '   If RectRadius > Rectwidth / 2 OrElse RectRadius >
      '   RectHeight / 2 Then RectRadius = RectHeight / 2
      ''''''''''

      pRRPath.StartFigure()

      pRRPath.AddArc(RectX, RectY, RectRadius * 2, _
                     RectRadius * 2, 180, 90)
      pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, RectY, _
                     RectRadius * 2, RectRadius * 2, 270, 90)
      pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, _
                     RectY + RectHeight - RectRadius * 2, _
                     RectRadius * 2, RectRadius * 2, 0, 90)
      pRRPath.AddArc(RectX, RectY + RectHeight - RectRadius * 2, _
                     RectRadius * 2, RectRadius * 2, 90, 90)
      pRRPath.CloseFigure()

      gCanvas.DrawPath(pRoundRectPen, pRRPath)
   End If

   'Was The Fill Tool Clicked
   If blnFillDrawClicked Then
      'Yes, Set The Brush Color To The Selected Color

      Dim RectX      As Integer = Math.Min(sStartX, sEndX)
      Dim RectY      As Integer = Math.Min(sStartY, sEndY)
      Dim RectWidth  As Integer = Math.Abs(sStartX - sEndX)
      Dim RectHeight As Integer = Math.Abs(sStartY - sEndY)
      Dim RectRadius As Integer = 15

      'Fill The Shape Being Drawn
      Dim pRRPath As New System.Drawing.Drawing2D.GraphicsPath

      'Part 4
      'Removed, Caused Error If Only Clicked Once, Instead Of
      'Dragging
      'Now, This Gives A Sniper Type CrossHair When Clicked Once
      '   If RectRadius > Rectwidth / 2 OrElse RectRadius >
      '   RectHeight / 2 Then RectRadius = RectHeight / 2
      '''''''''''

      pRRPath.StartFigure()

      pRRPath.AddArc(RectX, RectY, RectRadius * 2, _
                     RectRadius * 2, 180, 90)
      pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, _
                     RectY, RectRadius * 2, RectRadius * 2, 270, 90)
      pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, _
                     RectY + RectHeight - RectRadius * 2, _
                     RectRadius * 2, RectRadius * 2, 0, 90)
      pRRPath.AddArc(RectX, RectY + RectHeight - RectRadius * 2, _
                     RectRadius * 2, RectRadius * 2, 90, 90)
      pRRPath.CloseFigure()

      Select Case TBrushType
         Case modCanvas.BrushType.brHorizontal
            Dim hrzRoundRectBrush As New _
               HatchBrush(HatchStyle.Horizontal, cColor, _
                          picCDraw.BackColor)

            gCanvas.FillPath(hrzRoundRectBrush, pRRPath)
         Case modCanvas.BrushType.brLinear
            Dim linRoundRectBrush As New _
               LinearGradientBrush(New Point(sStartX, sStartY), _
               New Point(sEndX, sEndY), cColor, picCDraw.BackColor)
            gCanvas.FillPath(linRoundRectBrush, pRRPath)
         Case modCanvas.BrushType.brPath

            ' Use the path to construct a brush.
            Dim pthRoundRectGrBrush As New _
               PathGradientBrush(pRRPath)

            pthRoundRectGrBrush.CenterColor = cColor


            Dim pthColors As Color() = _
               {Color.FromArgb(40, cColor.R, cColor.G, cColor.B)}
            pthRoundRectGrBrush.SurroundColors = pthColors

            gCanvas.FillPath(pthRoundRectGrBrush, pRRPath)

         Case modCanvas.BrushType.brPlaid
            Dim pldRoundRectBrush As New _
               HatchBrush(HatchStyle.Plaid, cColor)
            gCanvas.FillPath(pldRoundRectBrush, pRRPath)
         Case modCanvas.BrushType.brSolid
            Dim sbRoundRectBrush As New SolidBrush(cColor)
            gCanvas.FillPath(sbRoundRectBrush, pRRPath)
         Case modCanvas.BrushType.brTexture
            Dim txRoundRectImage = New Bitmap(strTextureFile)
            Dim txRoundRectBrush As New _
               TextureBrush(txRoundRectImage)
            txRoundRectBrush.WrapMode = WrapMode.TileFlipY
            gCanvas.FillPath(txRoundRectBrush, pRRPath)

         Case modCanvas.BrushType.brVertical
            Dim vrtRoundRectBrush As New _
               HatchBrush(HatchStyle.Vertical, cColor, _
                          picCDraw.BackColor)
            gCanvas.FillPath(vrtRoundRectBrush, pRRPath)
         Case modCanvas.BrushType.brWeave
            Dim wvRoundRectbrush As New _
               HatchBrush(HatchStyle.Weave, cColor, _
                          picCDraw.BackColor)
            gCanvas.FillPath(wvRoundRectbrush, pRRPath)
         Case modCanvas.BrushType.brZigZag
            Dim zzRoundRectbrush As New _
               HatchBrush(HatchStyle.ZigZag, cColor, _
                          picCDraw.BackColor)
            gCanvas.FillPath(zzRoundRectbrush, pRRPath)
      End Select


   End If

End If

This produces results when you only click on the Canvas. Ehile this tool is active, it produces a "Sniper crosshair." I initally wanted to remove this as well, but decided to leave it as is, because it actually looks quite funky—at least to me.

Fixing the Hatch Brush Filling

Based on various comments I received, through email, I realised that I needed to fix this issue as well. Basically, what happened was that when you have drawn objects, and selected Draw then Fill, and choose any other option except for a Hatch Brush, and right-clicked to fill the image, it produced an exception. The quick route I took to fix this issue was to create Boolean flag, to determine whether a HatchBrush tool has been selected or not. If a Hatchbrush is selected, this will be set to true; else, it will be set to false. Before filling, I test the value of this boolean flag, and only fill the shape if it is set to true.

The attached project contains all of the above changes and updates. Feel free to explore and further modify it.

Conclusion

I sincerely hope that you have benefited from this article, and that you have enjoyed reading it. Please watch out for Part 5 of this article series (which would be the last), where we are going to do more advanced drawings and selections. Who knows, there may be a few surprises too.



About the Author

Hannes du Preez

Hannes du Preez is a Microsoft MVP for Visual Basic. He is a trainer at a South African-based company. He is the co-founder of hmsmp.co.za, a community for South African developers.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The first phase of API management was about realizing the business value of APIs. This next wave of API management enables the hyper-connected enterprise to drive and scale their businesses as API models become more complex and sophisticated. Today, real world product launches begin with an API program and strategy in mind. This API-first approach to development will only continue to increase, driven by an increasingly interconnected web of devices, organizations, and people. To support this rapid growth, …

  • 10 Rules that Make or Break Enterprise App Development Projects In today's app-driven world, application development is a top priority. Even so, 68% of enterprise application delivery projects fail. Designing and building applications that pay for themselves and adapt to future needs is incredibly difficult. Executing one successful project is lucky, but making it a repeatable process and strategic advantage? That's where the money is. With help from our most experienced project leads and software engineers, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds