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:
- I added a Preview form
- I added Selection Tools
- I added Cropping functionality
- I added Zooming capabilities
- Rotation abilities
- Drawing Text
- Changing the canvas background colour
- 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:
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.
To add the Preview form, follow these simple steps
- From the Visual Basic IDE, select Project, Add Windows Form...
- Name the form frmPreview.
- Set frmPreview's AutoScroll property to True; this will produce scrollbars on your form so that you can scroll a large image.
- Set frmPreview's FormBorderStyle property to SizableToolWindow.
- Set frmPreview's BackColor to White.
- Set the ShowInTaskbar property to False, so that this form's title doesn't show on the Taskbar.
- Set the Text property to Preview.
- Add a Picturebox named picPreview to this new form.
- Set picPreview's BackColor property to White.
- Set picPreview's Width to 900—to make sure your selections don't get cropped.
- Set picPreview's Height to 500.
Your Preview form is now created.
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
- Open frmTools and add a new Button, named btnCSelect.
- Set the FlatStyle property to Popup, so that it looks & acts the same as your other buttons on frmTools.
- Set the Width to 75.
- Set the Height to 23.
- 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.
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:
blnSelNormal = False
In frmTools_Load, you also need to add a Tooltip for your new button:
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:
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