|
Before discussing the UML class diagram editor project in detail, let's look at a much-simplified version of the project. In fact, this simple drawing application can draw one shape only: rectangles. Understanding this simple application will help you understand the project.
The application has two classes: the DrawArea class that extends System.Windows.Forms.Panel and the form. The draw area fills the whole client area of the form. To draw a rectangle, the user clicks on the draw area and drags the mouse along. The first click point is the top-left corner of the rectangle. The point where the user releases the mouse becomes the bottom-right corner of the rectangle. Because you cannot guarantee that the user will drag the mouse to the left and down when drawing a rectangle, the release point could be located left of the starting point. If this is the case, then you have a problem because the two constructors of the System.Drawing.Rectangle class require you to pass a Point object representing the upper-left corner of the rectangle or the coordinate of that point.
The solution to this problem is the DrawArea class's GetRectangleFromPoints method, which you can use to obtain a System.Drawing.Rectangle object. The two points passed to this method can be any points in the draw area. You will see the GetRectangleFromPoints method after looking at the DrawArea class.
The DrawArea class extends the System.Windows.Forms.Panel class (see Listing 4-6).
Listing 4-6: The DrawArea Class
Imports System.Drawing Imports System.Collections Imports System.Windows.Forms Public Class DrawArea : Inherits Panel Private shapes As New ArrayList() Private startPoint As Point Public Sub New() AddHandler Me.MouseDown, AddressOf me_MouseDown AddHandler Me.MouseUp, AddressOf me_MouseUp End Sub Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics Dim shapeEnum As IEnumerator = shapes.GetEnumerator Dim blackPen As Pen = Pens.Black While shapeEnum.MoveNext Dim rectangle As Rectangle = CType(shapeEnum.Current, Rectangle) g.DrawRectangle(blackPen, rectangle) End While End Sub Private Sub me_MouseDown(ByVal sender As System.Object, _ ByVal e As MouseEventArgs) If e.Button = MouseButtons.Left Then startPoint = New Point(e.X, e.Y) End If End Sub Private Sub me_MouseUp(ByVal sender As System.Object, _ ByVal e As MouseEventArgs) If e.Button = MouseButtons.Left Then Dim endPoint As New Point(e.X, e.Y) Dim r As Rectangle = GetRectangleFromPoints(startPoint, endPoint) shapes.Add(r) Me.Refresh() End If End Sub Public Shared Function GetRectangleFromPoints(ByVal p1 As Point, _ ByVal p2 As Point) As Rectangle Dim x1, x2, y1, y2 As Integer If p1.X < p2.X Then x1 = p1.X x2 = p2.X Else x1 = p2.X x2 = p1.X End If If p1.Y < p2.Y Then y1 = p1.Y y2 = p2.Y Else y1 = p2.Y y2 = p1.Y End If ' x2 > x1 and y2 > y1 Return New Rectangle(x1, y1, x2 - x1, y2 - y1) End Function End Class
Note | To compile the DrawArea class in Listing 4-6, use the following command from the directory in which the listing-04.06.vb file resides: vbc /t:library /r:System.dll,System.Windows.Forms.dll, System.Drawing.dll listing-04.06.vb (all on one line). |
The DrawArea class has a private System.Collection.ArrayList object in which all Rectangle objects drawn are stored. The DrawArea class overrides the OnPaint method so that all rectangles can be redrawn whenever the Paint event triggers.
In the constructor, the DrawArea class wires the MouseDown event with the me_MouseDown event handler and the MouseUp event with the me_MouseUp event handler:
AddHandler Me.MouseDown, AddressOf me_MouseDown AddHandler Me.MouseUp, AddressOf me_MouseUp
The me_MouseDown event handler is called whenever the user clicks on the DrawArea object. The event handler is passed a MouseEventArgs object, from which you can obtain the Button object of the mouse and the coordinate of the click point (in the form of X and Y properties). What the me_MouseDown event does is check if the mouse button clicked was MouseButton.Left. If it was, it constructs a Point object containing the clicked point coordinate and assigns the Point object to the startPoint variable:
If e.Button = MouseButtons.Left Then startPoint = New Point(e.X, e.Y) End If
The me_MouseUp event handler is invoked when the user releases the mouse button. It first checks if the mouse button released was MouseButtons.Left. If it was, it constructs a Point object containing the point at which the user released the mouse button and assigns it to the local variable endPoint.
Dim endPoint As New Point(e.X, e.Y)
It then passes both points to the GetRectangleFromPoints method to obtain a Rectangle object and add the Rectangle object to the shapes ArrayList:
Dim r As Rectangle = GetRectangleFromPoints(startPoint, endPoint) shapes.Add(r)
Finally, it calls the Refresh method to force the surface to be repainted:
Me.Refresh()
You need the GetRectangleFromPoints method because you cannot guarantee that the startPoint will be on the top-left of the end point. The GetRectangleFromPoints method checks the coordinate of the two points and creates new points, if necessary, so that the first point will have an x coordinate and a y coordinate whose values are less than the values of the x and y coordinates of the second point.
Then, it constructs a Rectangle object by passing the two new coordinates and returns the Rectangle object.
The form in the application adds a DrawArea object as its child control and sets its Dock property to DockStyle.Fill. It also changes the background color of the DrawArea object to white.
Listing 4-7 shows the form.
Listing 4-7: The Form
Imports System Imports System.Windows.Forms Imports System.Drawing Imports System.Drawing.Drawing2D Public Class Form1 : Inherits Form Private drawArea As New DrawArea() Public Sub New() Me.ClientSize = New System.Drawing.Size(740, 585) drawArea.Dock = DockStyle.Fill Me.Controls.Add(drawArea) drawArea.BackColor = Color.White End Sub <STAThread()> Shared Sub Main() Dim f As New Form1() Application.Run(f) End Sub End Class
Note | To compile the form in Listing 4-7, use the following command from the directory in which the listing-04.07.vb file resides: vbc /t:winexe /r:System.dll,System.Windows.Forms.dll, System.Drawing.dll,listing-04.06.dll listing-04.07.vb (all on one line). |
If you run the application, you can click and drag to draw a Rectangle object.
However, you probably notice one thing. When you drag your mouse, there is nothing to remind you of where the start point is. You also cannot see the size of the rectangle you are drawing.
To make it look more professional, you can add a feature so that you can see the rectangle you are drawing. The main thing you need to do is capture one more event: MouseMove.
To add the new feature, do the following steps to the DrawArea class:
Add the movingEndPoint variable of type Point in the class declaration:
Private movingEndPoint As Point
Wire the MouseMove event to the me_MouseMove event handler in the constructor. The new constructor will look like this:
Public Sub New() AddHandler Me.MouseDown, AddressOf me_MouseDown AddHandler Me.MouseMove, AddressOf me_MouseMove AddHandler Me.MouseUp, AddressOf me_MouseUp End Sub
Assign the movingEndPoint value to the startPoint every time the mouse is clicked. In the me_MouseDown event handler, add the line of code in bold:
Private Sub me_MouseDown(ByVal sender As System.Object, _ ByVal e As MouseEventArgs) If e.Button = MouseButtons.Left Then startPoint = New Point(e.X, e.Y) movingEndPoint = startPoint End If End Sub
Add the me_MouseMove event handler to handle the MouseMove event. This event triggers when the user drags the mouse. You need to draw a temporary rectangle and remove the previous temporary rectangle. This is the me_MouseMove event handler:
Private Sub me_MouseMove(ByVal sender As System.Object, _ ByVal e As MouseEventArgs) If e.Button = MouseButtons.Left Then Dim endPoint As New Point(e.X, e.Y) Dim graphics As Graphics = Me.CreateGraphics 'remove the previous rectangle graphics.DrawRectangle(Pens.White, _ GetRectangleFromPoints(startPoint, movingEndPoint)) movingEndPoint = endPoint Me.Refresh() 'draw a temporary rectangle graphics.DrawRectangle(Pens.Black, _ GetRectangleFromPoints(startPoint, movingEndPoint)) End If End Sub
Listing 4-8 gives the updated DrawArea class. The Form1 class remains unchanged.
Listing 4-8: The DrawArea Class That Captures the MouseMove Event
Imports System.Drawing Imports System.Collections Imports System.Windows.Forms Public Class DrawArea : Inherits Panel Private shapes As New ArrayList() Private startPoint As Point Private movingEndPoint As Point Public Sub New() AddHandler Me.MouseDown, AddressOf me_MouseDown AddHandler Me.MouseMove, AddressOf me_MouseMove AddHandler Me.MouseUp, AddressOf me_MouseUp End Sub Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics Dim shapeEnum As IEnumerator = shapes.GetEnumerator Dim blackPen As Pen = Pens.Black While shapeEnum.MoveNext Dim rectangle As Rectangle = CType(shapeEnum.Current, Rectangle) g.DrawRectangle(blackPen, rectangle) End While End Sub Private Sub me_MouseDown(ByVal sender As System.Object, _ ByVal e As MouseEventArgs) If e.Button = MouseButtons.Left Then startPoint = New Point(e.X, e.Y) movingEndPoint = startPoint End If End Sub Private Sub me_MouseMove(ByVal sender As System.Object, _ ByVal e As MouseEventArgs) If e.Button = MouseButtons.Left Then Dim endPoint As New Point(e.X, e.Y) Dim graphics As Graphics = Me.CreateGraphics graphics.DrawRectangle(Pens.White, _ GetRectangleFromPoints(startPoint, movingEndPoint)) movingEndPoint = endPoint Me.Refresh() graphics.DrawRectangle(Pens.Black, _ GetRectangleFromPoints(startPoint, movingEndPoint)) End If End Sub Private Sub me_MouseUp(ByVal sender As System.Object, _ ByVal e As MouseEventArgs) If e.Button = MouseButtons.Left Then Dim endPoint As New Point(e.X, e.Y) Dim r As Rectangle = GetRectangleFromPoints(startPoint, endPoint) shapes.Add(r) Me.Refresh() End If End Sub Public Shared Function GetRectangleFromPoints(ByVal p1 As Point, _ ByVal p2 As Point) As Rectangle Dim x1, x2, y1, y2 As Integer If p1.X < p2.X Then x1 = p1.X x2 = p2.X Else x1 = p2.X x2 = p1.X End If If p1.Y < p2.Y Then y1 = p1.Y y2 = p2.Y Else y1 = p2.Y y2 = p1.Y End If ' x2 > x1 and y2 > y1 Return New Rectangle(x1, y1, x2 - x1, y2 - y1) End Function End Class
|