Implementing the Project


You will start the project by defining the project specification. In plain English, it is a Windows application that can do the following:

  • Draw two types of rectangular structures and four types of lines. A line always connects two rectangular structures and cannot stand alone.

  • Write labels on the rectangular structure and attach labels on the line.

  • Resize and move each rectangular structure around the draw area.

  • Automatically adjust the line position when either of its rectangular structure is resized or moved.

  • Allow the user to manually move a line around its rectangular structure.

The two rectangular structures available are the class structure and the interface structure. Figure 4-9 shows the class structure, and Figure 4-10 shows the interface structure.


Figure 4-9: The class structure


Figure 4-10: The interface structure

As shown in Figure 4-9, a class structure has three partitions. The top-most partition is for the class name. The one in the middle is for the class's list of attributes, and the bottom one is for the list of operations. Figure 4-11 illustrates a class with a name (Rectangle), four attributes (left, top, height, and width) and two operations (Draw() and Move()).


Figure 4-11: A class with a name, attributes, and operations

An interface structure is similar to a class structure, but it only has two partitions. The top partition is for the interface name, which always follows the word <Interface>. The bottom partition is for the interface's list of operations. Figure 4-12 shows an interface called IButtonControl with two operations: NotifyDefault and PerformClick.


Figure 4-12: An interface with a name and operations

Two structures can have a relationship between them. A line represents a relationship. Four types of relationships are provided using one of the lines in Figure 4-13.

click to expand
Figure 4-13: Types of relationships

As an example, Figure 4-14 shows a generalization relationship between the class Rectangle and the class Shape. In the figure, Rectangle is a child class of Shape.

click to expand
Figure 4-14: A generalization relationship

Note

A line always has two structures at its ends. It cannot stand alone. If one of the structures it connects is deleted, the line will be automatically removed, too.

By default, a line connecting two structures will move automatically when one of the structures is moved. The position of the line will depend on the relative position between the two structures it connects. Figure 4-15 shows the line positions in four relative positions of the two structures.

click to expand
Figure 4-15: Automatic adjustments of the line positions

Automatic adjustment of a line's position that is caused by moving one of its structures always places the line at the center of one of the structure's edges. The user is allowed to adjust the line manually by dragging the line. Figure 4-16 shows a line that has been moved manually.

click to expand
Figure 4-16: Manually positioning a line

A line can have zero to three labels. You can place each label on the left, center, and the right of the line. Figure 4-17 shows a line with labels.

click to expand
Figure 4-17: Line with labels

Finally, you can select a structure and a line to manipulate individually. For example, to add a name to a class structure, you must first select that class. A selected structure or line will be the active object and have handles. Figure 4-18 shows a selected class, and Figure 4-19 shows a selected line.

click to expand
Figure 4-18: A selected class structure

click to expand
Figure 4-19: A selected line

The UML class diagram editor project is a Windows application. Figure 4-20 shows the class diagram for this application.

click to expand
Figure 4-20: The class diagram

The main form has a number of menu items, a toolbar, a status bar, and a draw area. The draw area is represented by the DrawArea class, which derives from the System.Windows.Forms.Panel class. The draw area fills the entire client area of the main form.

The class structure is represented by the ClassRect class, and the interface structure is represented by the InterfaceRect class. Both ClassRect and InterfaceRect are direct child classes of the Rect class. The Line class represents a line.

The Rect class derives from the System.Windows.Forms.UserControl class and can draw itself. Every rectangular shape is added as a child control of the DrawArea object.

The Line class, on the other hand, does not inherit from any Windows control class. Therefore, it does not have a drawing surface to draw itself. As a result, it must use the Graphics object of the DrawArea control to draw itself. Every line drawn is added to an ArrayList of the DrawArea object.

Both rectangular shapes and lines have an internal state called selected that indicates whether the shape is being selected. In addition, each shape has the index field that stores a number that is unique to that shape. This unique number is passed to the constructor of both the Rect class and the Line class.

You can persist the shapes drawn into a file and later retrieve them from the same file.

The types used in this application are as follows:

  • The ShapeType enumeration

  • The RectHandle enumeration

  • The LineHandle enumeration

  • The RectPart enumeration

  • The States class

  • MathUtil

  • Rect

  • ClassRect

  • InterfaceRect

  • ClassRectMemento

  • InterfaceRectMemento

  • Line

  • LineMemento

  • InputBox

  • PropertyForm

  • DrawArea

  • Form1

The following sections discuss each type, from the simplest classes to the more complex.

The ShapeType Enumeration

The ShapeType enumeration provides a way to enumerate the shapes that can be drawn plus None. Listing 4-13 gives the ShapeType enumeration, which you can find in the Misc.vb file in the project directory.

Listing 4-13: The ShapeType Enumeration

start example
 Imports System.Windows.Forms Public Enum ShapeType As Integer   [Class]   [Interface]   Generalization   Dependency   Association   Aggregation   None End Enum 
end example

The RectHandle Enumeration

When selected, a class structure or an interface structure has four handles. Each handle is denoted by one of the members of the RectHandle enumeration. Listing 4-14 gives the RectHandle enumeration, which you can find in the Misc.vb file in the project directory.

Listing 4-14: The RectHandle Enumeration

start example
 Public Enum RectHandle As Integer   TopLeft   TopRight   BottomLeft   BottomRight   None End Enum 
end example

Figure 4-21 shows a selected class structure.

click to expand
Figure 4-21: A selected class structure

The LineHandle Enumeration

When selected, a line has two handles. LineHandle.FromHandle denotes the handle on the start point. LineHandle.ToHandle denotes the handler on the end point. Listing 4-15 shows the LineHandle enumeration, which you can find in the Misc.vb file in the project directory.

Listing 4-15: The LineHandle Enumeration

start example
 Public Enum LineHandle As Integer   FromHandle   ToHandle   None End Enum 
end example

The RectPart Enumeration

When manipulating the parts of a class or interface, you need to refer to that part by using the RectPart enumeration. Listing 4-16 shows RectPart, which you can find in the Misc.vb file in the project directory.

Listing 4-16: The RectPart Enumeration

start example
 Public Enum RectPart As Integer   Name   Attributes   Operations End Enum 
end example

A class has three parts: name, attributes, and operations. An interface has two parts: name and operations.

The States Class

The States class stores two values that are shared by other classes in the application. Listing 4-17 shows this class, which you can find in the Misc.vb file in the project directory.

Listing 4-17: The States Class

start example
 Public Class States   Public Shared ShapeDrawn As ShapeType = ShapeType.None   Public Shared RectPart As RectPart End Class 
end example

The first value is ShapeDrawn of type ShapeType. Its value can be one of the members of the ShapeType enumeration. It tells the application what shape the user is drawing. The user can choose which shape to be drawn by clicking one of the toolbar buttons representing those shapes. For example, if a user clicks the toolbar button representing the class structure, the event handler of that click event changes the value of States.ShapeDrawn. The DrawArea object then uses this value to respond to the user mouse click and drag.

The second value is RectPart of type RectPart. It tells which part of a class structure or an interface structure is being manipulated. The user can edit the label in those parts.

The MathUtil Class

The MathUtil class contains three methods, all of which are static. The first two methods are public static methods: GetRectangleFromPoints and GetSquareDistance. The third method is a private overload of GetSquareDistance. The private static method GetSquareDistance is used by the public GetSquareDistance method. Listing 4-18 shows the MathUtil class. Each method is explained after the code.

Listing 4-18: The MathUtil Class

start example
 Option Explicit On Option Strict On Imports System Imports System.Drawing Public Class MathUtil   Public Shared Function GetSquareDistance(ByVal p1 As Point, _     ByVal p2 As Point, ByVal p3 As Point) As Double     ' calculate the distance between P3 and the line passing P1 and P2     Dim aSquare, bSquare, cSquare, d, eSquare As Double     aSquare = GetSquareDistance(p1, p3)     bSquare = GetSquareDistance(p2, p3)     cSquare = GetSquareDistance(p1, p2)     ' c should not be zero, otherwise it means p1 = p2     ' if it is so, return the distance of p3 and p1     If cSquare = 0 Then       Return Math.Sqrt(aSquare)     End If     d = (bSquare - aSquare + cSquare) / (2 * Math.Sqrt(cSquare))     eSquare = bSquare - d ^ 2     Return Math.Abs(eSquare)   End Function   Private Shared Function GetSquareDistance(ByVal p1 As Point, _     ByVal p2 As Point) As Double     Return ((p2.X - p1.X) ^ 2 + (p2.Y - p1.Y) ^ 2)   End Function   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 
end example

The GetRectangleFromPoints method is the same method explained in the section "Creating a Simple Drawing Application". It returns a System.Drawing.Rectangle object given two Point objects.

The public GetSquareDistance method obtains the square distance between a point (p1) and a line passing two points (p2 and p3). The private GetSquareDistance returns the square distance between two points. Before delving into these two methods, however, let's have a look at some math. Those who are allergic to mathematics should not worry because there is only one simple mathematical formula: Pythagoras's Theorem. It says that in a right-angled triangle, the area of the square on the hypotenuse is the sum of the areas of the squares on the other two sides (see Figure 4-22).

click to expand
Figure 4-22: Pythagoras's Theorem

Note

Pythagoras's Theorem is c2 = a2 + b2.

The following shows how you can use Pythagoras's Theorem to find the distance between a point and a line if you know the coordinate of the point and the two points passed by the line.

The distance between point P3 and the line is the closest distance to the line at the tangent to the line that passes through P3. In Figure 4-23, the distance between P3 and the line passing P1 and P2 is e.

click to expand
Figure 4-23: The distance between a point and a line

Applying Pythagoras's Theorem, you get the illustration in Figure 4-24.

click to expand
Figure 4-24: Using Pythagoras's Theorem to find the distance between a point and a line

Using Pythagoras's Theorem on the two triangles in Figure 4-24, you get the following:

Equating (i) and (ii), you get the following:

From (ii) you know the following:

 e2 = b2 - d2 

Therefore, the distance is as follows:

where d is known from (iii).

Because you know the coordinates of the start point and the end point of a line in the class diagram, you know the distance between the two points P1 (x1, y1) and P2 (x2, y2) in Figure 4-24 is as follows:

 Sqrt((x1 - x2) 2 + (y1 - y2) 2) 

Therefore, you know c in (iii) previously. You also know a and b by using the same formulae to obtain the distance between P1 and P3 and the distance between P2 and P3. Therefore, you know a and b in (iii).

You use the private GetSquareDistance method to obtain the square distance between two points:

 Private Shared Function GetSquareDistance(ByVal p1 As Point, _   ByVal p2 As Point) As Double   Return ((p2.X - p1.X) ^ 2 + (p2.Y - p1.Y) ^ 2) End Function 

Using (iii) and (iv), you can get the square distance of point P3 and the line passing P1 and P2 and calculate it using the public GetSquareDistance method in the MathUtil class. Both methods return the square of the distance to avoid rounding. In fact, knowing the square distance is as useful as knowing the distance itself.

The shared public GetSquareDistance method determines which line is closest to a click point on the draw area. It is called by the GetNearestLine method in the DrawArea class to determine which line is closest to a click point.

The Rect Class

The Rect class is an abstract class that represents a rectangular area and derives from the System.Windows.Forms.UserControl class. It has two child classes: ClassRect and InterfaceRect. ClassRect represents the class structure, and the InterfaceRect represents the interface structure.

Listing 4.19 shows the Rect class.

Listing 4-19: The Rect Class

start example
 Option Explicit On Option Strict On Imports System Imports System.Windows.Forms Imports System.Drawing Imports System.Collections Imports Microsoft.VisualBasic Public MustInherit Class Rect : Inherits UserControl   Public StartPoint As Point   Public EndPoint As Point   Public selected As Boolean = True   Private handleColor As Color = Color.Red   Public Const handleWidth As Integer = 6   Public minimumWidth As Integer = 50   Public minimumHeight As Integer = 100   Protected foregroundPen As New Pen(Color.Black)   Private handleBrush As New SolidBrush(handleColor)   Protected textBrush As New SolidBrush(Color.Black)   Public Shared Shadows fontHeight As Integer = 12   Public Shared textTopMargin As Integer = 5   Public index As Integer   Protected yPos As Integer   Protected xPos As Integer = 5   Public operations As New ArrayList()   Public Sub New(ByVal startPoint As Point, ByVal endPoint As Point, _     ByVal index As Integer)     Me.StartPoint = startPoint     Me.EndPoint = endPoint     Me.index = index     Dim r As Rectangle = MathUtil.GetRectangleFromPoints(startPoint, endPoint)     Dim currentWidth As Integer = _       CInt(IIf(r.Width > minimumWidth, r.Width, minimumWidth))     Dim currentHeight As Integer = _       CInt(IIf(r.Height > minimumHeight, r.Height, minimumHeight))     Me.SetBounds(r.X, r.Y, currentWidth, currentHeight)     Font = New Font("Times New Roman", 10)   End Sub   Public Sub Delete()     ' call Dispose to remove me from parent's Controls collection     Me.Dispose()     Me.DestroyHandle() 'trigger the HandleDestroyed event   End Sub   Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)     Dim g As Graphics = e.Graphics     If selected Then       g.DrawRectangle(foregroundPen, _         CInt(handleWidth / 2), CInt(handleWidth / 2), Me.Width - handleWidth, _         Me.Height - handleWidth)       DrawHandles(g)     Else       g.DrawRectangle(foregroundPen, _         0, 0, Me.Width - 1, Me.Height - 1)     End If     DrawName(g)     'draw the line that partition the name part and the next part     yPos += fontHeight     If selected Then       Dim x1 As Integer = CInt(handleWidth / 2)       Dim x2 As Integer = x1 + Me.Width - handleWidth       g.DrawLine(foregroundPen, x1, yPos, x2, yPos)     Else       Dim x1 As Integer = 0       Dim x2 As Integer = x1 + Me.Width       g.DrawLine(foregroundPen, x1, yPos, x2, yPos)     End If     DrawMembers(g)   End Sub   Private Sub DrawHandles(ByRef g As Graphics)     'draw handles     g.FillRectangle(handleBrush, _       0, 0, handleWidth, handleWidth)     g.FillRectangle(handleBrush, _       Me.Width - handleWidth, 0, handleWidth, handleWidth)     g.FillRectangle(handleBrush, _       0, Me.Height - handleWidth, handleWidth, handleWidth)     g.FillRectangle(handleBrush, _       Me.Width - handleWidth, Me.Height - handleWidth, _       handleWidth, handleWidth)   End Sub   Protected MustOverride Sub DrawName(ByRef g As Graphics)   Protected MustOverride Sub DrawMembers(ByRef g As Graphics)   Public Function OnWhichHandle(ByVal p As Point) As RectHandle     If Not selected Then       Return RectHandle.None     End If     Dim x As Integer = p.X     Dim y As Integer = p.Y     If x <= handleWidth And y <= handleWidth Then       Return RectHandle.TopLeft     ElseIf x >= Me.Width - handleWidth And y <= handleWidth Then       Return RectHandle.TopRight     ElseIf x <= handleWidth And y >= Me.Height - handleWidth Then       Return RectHandle.BottomLeft     ElseIf x >= Me.Width - handleWidth And y >= Me.Height - handleWidth Then       Return RectHandle.BottomRight     Else       Return RectHandle.None     End If   End Function   Public Sub SetTop(ByVal top As Integer)     If top >= 0 Then       Me.Top = top     End If     StartPoint.Y = Me.Top     EndPoint.Y = StartPoint.Y + Me.Height   End Sub   Public Sub SetLeft(ByVal left As Integer)     If left >= 0 Then       Me.Left = left     End If     StartPoint.X = Me.Left     EndPoint.X = StartPoint.X + Me.Width   End Sub   Public Sub SetWidth(ByVal width As Integer)     If width > minimumWidth Then       Me.Width = width     Else       Me.Width = minimumWidth     End If     EndPoint.X = StartPoint.X + Me.Width   End Sub   Public Sub SetHeight(ByVal height As Integer)     If height > minimumHeight Then       Me.Height = height     Else       Me.Height = minimumHeight     End If     EndPoint.Y = StartPoint.Y + Me.Height   End Sub End Class 
end example

The Rect Class's Constructor

The user draws a class structure or an interface structure by clicking the mouse on the draw area and dragging the mouse and then releasing the mouse. Therefore, there are two important Point objects when drawing: the Point object representing the click point on the draw area and the Point object representing the point on which the mouse click is released. The first point is called the start point, and the second point is called the end point.

The Rect class's child class constructor will invoke the Rect class's constructor, passing the start point and the end point as well as an integer that will become a unique identifier for the constructed Rect object. The index is generated in the DrawArea object and is a sequential number.

The startPoint, endPoint, and index arguments are assigned to the class-level variables:

 Me.StartPoint = startPoint Me.EndPoint = endPoint Me.index = index 

Again, because you cannot predict how users drag the mouse when drawing, you use the GetRectangleFromPoints method of the MathUtil class to obtain a Rectangle object from the start point and the end point:

 Dim r As Rectangle = MathUtil.GetRectangleFromPoints(startPoint, endPoint) 

You impose a minimum width and a minimum height for a Rect object. If the Rectangle object's width is narrower than the minimum width, the minimum width is used. If the Rectangle object's height is shorter than the minimum height, the minimum height is used. You get two values: currentWidth and currentHeight. For example:

 Dim currentWidth As Integer = _   CInt(IIf(r.Width > minimumWidth, r.Width, minimumWidth)) Dim currentHeight As Integer = _   CInt(IIf(r.Height > minimumHeight, r.Height, minimumHeight)) 

Next, set the bounds for the Rect object using the Rectangle object and currentWidth and currentHeight:

 Me.SetBounds(r.X, r.Y, currentWidth, currentHeight) 

Next, construct a Font object used for drawing the labels in the Rect object:

 Font = New Font("Times New Roman", 10) 

The OnPaint Method

You draw the rectangular area by overriding the OnPaint method. First you draw the rectangle around the Rect object and then around the name (by calling the DrawName method) and the members (by calling the DrawMembers method). To draw, you need to first obtain the Graphics object from the PaintEventArgs argument:

 Dim g As Graphics = e.Graphics 

The rectangle will depend on whether the Rect object is selected—in other words, it depends on the value of the selected field. If it is not selected, it draws the rectangle that has the same dimension as the Rect object:

 g.DrawRectangle(foregroundPen, _   0, 0, Me.Width - 1, Me.Height - 1) 

If it is selected, the rectangle is slightly smaller because you need to draw the four handles:

 g.DrawRectangle(foregroundPen, _   CInt(handleWidth / 2), CInt(handleWidth / 2), Me.Width - handleWidth, _   Me.Height - handleWidth) DrawHandles(g) 

Afterward, you need to draw the name by calling the DrawName method:

 DrawName(g) 

Then, you need to draw the partition line that separates the name and the next part (the attributes if the Rect object is a class structure, and the operation if it is an interface structure). Note that you keep the y position in the yPos variable:

 yPos += fontHeight 

Again, the line drawn will depend on whether the object is being selected:

 If selected Then   Dim x1 As Integer = CInt(handleWidth / 2)   Dim x2 As Integer = x1 + Me.Width - handleWidth   g.DrawLine(foregroundPen, x1, yPos, x2, yPos) Else   Dim x1 As Integer = 0   Dim x2 As Integer = x1 + Me.Width   g.DrawLine(foregroundPen, x1, yPos, x2, yPos) End If 

At the end, you call the DrawMembers method to draw the members:

 DrawMembers(g) 

Both the DrawName and the DrawMembers methods are abstract methods and must be overridden by the child class.

The SetTop, SetLeft, SetWidth, and SetHeight Methods

When a Rect object is moved, its Left and Top property values must also change. When the Rect object is resized, its Width and Height property values must also update. Rather than accessing these properties directly, you use the SetTop, SetLeft, SetWidth, and SetHeight methods so that you can restrict as well as modify some other values.

For instance, when the user tries to move the Rect object up further than the top edge of the DrawArea, the value of the top argument will be negative and the SetTop method will not allow it to happen because it checks the top argument:

 Public Sub SetTop(ByVal top As Integer)   If top >= 0 Then     Me.Top = top   End If   StartPoint.Y = Me.Top   EndPoint.Y = StartPoint.Y + Me.Height End Sub 

Also, when the user tries to move the Rect object past the left edge of the DrawArea, the left argument value of the SetLeft method will be negative and it will not allow it to happen:

 Public Sub SetLeft(ByVal left As Integer)   If left >= 0 Then     Me.Left = left   End If StartPoint.X = Me.Left   EndPoint.X = StartPoint.X + Me.Width End Sub 

The SetWidth and SetHeight method restrict the smallest width and height a Rect object can get:

 Public Sub SetWidth(ByVal width As Integer)   If width > minimumWidth Then     Me.Width = width   Else     Me.Width = minimumWidth   End If   EndPoint.X = StartPoint.X + Me.Width End Sub Public Sub SetHeight(ByVal height As Integer)   If height > minimumHeight Then     Me.Height = height   Else     Me.Height = minimumHeight   End If   EndPoint.Y = StartPoint.Y + Me.Height End Sub 

The OnWhichHandle Method

When the Rect object is selected, the user can resize it by dragging one of the four handles. The first thing to check is whether the mouse is over any handle and, if it is, which handle. The OnWhichHandle method returns a member of the RectHandle enumeration. If the mouse is over the Rect object but not over one of the handles, or if the Rect object is not selected, the method returns RectHandle.None.

The Delete Method

The Delete method is called when the DrawArea object receives a Delete input key when the Rect object is selected. The Delete method calls two methods:

 Me.Dispose() Me.DestroyHandle() 'trigger the HandleDestroyed event 

The Dispose method removes it from the Controls collection of the DrawArea method. The DestroyHandle method is called so that the HandleDestroyed event triggers. The HandleDestroyed event will be caught by any Line object connecting this Rect object to another Rect object. When the Line object receives this event, the Line object knows that one of its structures is deleted and therefore will remove itself.

The ClassRect Class

The ClassRect class represents a class structure. It derives from the Rect class. Listing 4-20 shows the ClassRect class.

Listing 4-20: The ClassRect Class

start example
 Option Explicit On Option Strict On Imports System Imports System.Windows.Forms Imports System.Drawing Imports System.Collections Imports Microsoft.VisualBasic Public Class ClassRect : Inherits Rect   Public attributes As New ArrayList()   Public Sub New(ByVal startPoint As Point, ByVal endPoint As Point, _     ByVal index As Integer)     MyBase.new(startPoint, endPoint, index)   End Sub   Public Sub New(ByVal memento As ClassRectMemento)     MyBase.New(memento.StartPoint, memento.EndPoint, memento.index)     Me.Name = memento.name     Me.attributes = memento.attributes     Me.operations = memento.operations     Me.selected = memento.selected   End Sub   Public ReadOnly Property AttributeCount() As Integer     Get       Return attributes.Count     End Get   End Property   Protected Overrides Sub DrawName(ByRef g As Graphics)     yPos = textTopMargin     ' center text     Dim x As Integer = _       CInt((Me.Width - g.MeasureString(Me.Name, Font).Width) / 2)     If x > xpos Then       g.DrawString(Name, Font, textBrush, x, yPos)     Else       g.DrawString(Name, Font, textBrush, xPos, yPos)     End If     yPos += fontHeight   End Sub   Protected Overrides Sub DrawMembers(ByRef g As Graphics)     'draw attributes here     Dim attrEnum As IEnumerator = attributes.GetEnumerator     While attrEnum.MoveNext       Dim attribute As String = CType(attrEnum.Current, String)       g.DrawString(attribute, Font, textBrush, xpos, ypos)       yPos += fontHeight     End While     'draw the line that partition the attributes and operations     yPos += fontHeight     If selected Then       Dim x1 As Integer = CInt(handleWidth / 2)       Dim x2 As Integer = x1 + Me.Width - handleWidth       g.DrawLine(foregroundPen, x1, yPos, x2, yPos)     Else       Dim x1 As Integer = 0       Dim x2 As Integer = x1 + Me.Width       g.DrawLine(foregroundPen, x1, yPos, x2, yPos)     End If     'draw operations     Dim opsEnum As IEnumerator = operations.GetEnumerator     While opsEnum.MoveNext       Dim operation As String = CType(opsEnum.Current, String)       g.DrawString(operation, Font, textBrush, xpos, ypos)       yPos += fontHeight     End While   End Sub   Public Function GetMemento() As ClassRectMemento     Dim memento As New ClassRectMemento()     memento.index = Me.index     memento.name = Me.Name     memento.attributes = Me.attributes     memento.operations = Me.operations     memento.StartPoint = Me.StartPoint     memento.EndPoint = Me.EndPoint     memento.selected = Me.selected     Return memento   End Function End Class 
end example

The ClassRect Class's Constructors

The ClassRect class has two constructors. The first constructor is as follows:

 Public Sub New(ByVal startPoint As Point, ByVal endPoint As Point, _   ByVal index As Integer)   MyBase.new(startPoint, endPoint, index) End Sub 

This constructor is called when the user has just drawn a class structure. This constructor simply calls the constructor in the base class (the Rect class).

The second constructor is called when the ClassRect object needs to be restored from the ClassRectMemento object. It accepts a ClassRectMemento object. This constructor calls the base class's constructor and restores any needed states from the RectMemento object:

 MyBase.New(memento.StartPoint, memento.EndPoint, memento.index) Me.Name = memento.name Me.attributes = memento.attributes Me.operations = memento.operations Me.selected = memento.selected 

The GetMemento Method

The GetMemento method returns a ClassRectMemento object for object serialization.

The DrawName Method

The DrawName method draws the name for this class structure.

The DrawMembers Method

The DrawMembers method draws the list of attributes and operations in this class structure.

The ClassRectMemento Class

The ClassRectMemento class is the memento for the ClassRect object (see Listing 4-21). Note that it is marked with the <Serializable> attribute so that it can be serialized.

Listing 4-21: The ClassRectMemento Class

start example
 Imports System Imports System.Drawing Imports System.Collections <Serializable()> Public Class ClassRectMemento   Public index As Integer   Public name As String   Public StartPoint As Point   Public EndPoint As Point   Public selected As Boolean = True   Public operations As ArrayList   Public attributes As ArrayList End Class 
end example

The InterfaceRect Class

The InterfaceRect class represents a UML interface structure. It derives from the Rect class (see Listing 4-22).

Listing 4-22: The InterfaceRect Class

start example
 Option Explicit On Option Strict On Imports System Imports System.Windows.Forms Imports System.Drawing Imports System.Collections Imports Microsoft.VisualBasic Public Class InterfaceRect : Inherits Rect   Public Sub New(ByVal startPoint As Point, ByVal endPoint As Point, _     ByVal index As Integer)     MyBase.New(startPoint, endPoint, index)   End Sub   Public Sub New(ByVal memento As InterfaceRectMemento)     MyBase.New(memento.StartPoint, memento.EndPoint, memento.index)     Me.Name = memento.name     Me.operations = memento.operations     Me.selected = memento.selected   End Sub   Protected Overrides Sub DrawName(ByRef g As Graphics)     yPos = textTopMargin     'draw prefix here     Dim s As String = "<<Interface>>"     Dim x As Integer = _       CInt((Me.Width - g.MeasureString(s, Font).Width) / 2)     If x > xpos Then       g.DrawString(s, Font, textBrush, x, yPos)     Else       g.DrawString(s, Font, textBrush, xPos, yPos)     End If     yPos += fontHeight     'draw name     ' center text     x = CInt((Me.Width - g.MeasureString(Me.Name, Font).Width) / 2)     If x > xpos Then       g.DrawString(Name, Font, textBrush, x, yPos)     Else       g.DrawString(Name, Font, textBrush, xPos, yPos)     End If     yPos += fontHeight   End Sub   Protected Overrides Sub DrawMembers(ByRef g As Graphics)     'draw operations here     'draw operations     Dim opsEnum As IEnumerator = operations.GetEnumerator     While opsEnum.MoveNext       Dim operation As String = CType(opsEnum.Current, String)       g.DrawString(operation, Font, textBrush, xpos, ypos)       yPos += fontHeight     End While   End Sub   Public Function GetMemento() As InterfaceRectMemento     Dim memento As New InterfaceRectMemento()     memento.index = Me.index     memento.name = Me.Name     memento.operations = Me.operations     memento.StartPoint = Me.StartPoint     memento.EndPoint = Me.EndPoint     memento.selected = Me.selected     Return memento   End Function End Class 
end example

The InterfaceRect Class's Constructors

The InterfaceRect class has two constructors. The first constructor is as follows:

 Public Sub New(ByVal startPoint As Point, ByVal endPoint As Point, _   ByVal index As Integer)   MyBase.new(startPoint, endPoint, index) End Sub 

This constructor is called when the user has just drawn an interface structure. This constructor just calls the constructor in the base class (the Rect class).

The second constructor is called when the InterfaceRect object needs to be restored from the InterfaceRectMemento object. It accepts an InterfaceRectMemento object. This constructor calls the base class's constructor and restores any needed states from the RectMemento object:

 MyBase.New(memento.StartPoint, memento.EndPoint, memento.index) Me.Name = memento.name Me.operations = memento.operations Me.selected = memento.selected 

The GetMemento Method

The GetMemento method returns an InterfaceRectMemento object for object serialization.

The DrawName Method

The DrawName method draws the name for this interface structure.

The DrawMembers Method

The DrawMembers method draws the list of operations in this interface structure.

The InterfaceRectMemento Class

The InterfaceRectMemento class represents the memento for the InterfaceRect object (see Listing 4-23). Note that this class is marked with the <Serializable> attribute to make it serializable.

Listing 4-23: The InterfaceRectMemento Class

start example
 Imports System Imports System.Drawing Imports System.Collections <Serializable()> Public Class InterfaceRectMemento   Public index As Integer   Public name As String   Public StartPoint As Point   Public EndPoint As Point   Public selected As Boolean = True   Public operations As ArrayList End Class 
end example

The InputBox Class

An InputBox object edits a class structure's name, list of attributes, and list of operations and edits a UML interface structure's name and list of operations. The InputBox object is represented by the InputBox class. The InputBox class derives from the System.Windows.Forms.TextBox class and is instantiated when the DrawArea control is constructed. The InputBox object is then added to the Controls collection of the DrawArea object. It is the first child control of the DrawArea object.

By default, the InputBox object is not visible. It is visible when the user rightclicks a class structure or an interface structure. When visible, the InputBox object is positioned on top of the clicked structure to give the impression that the InputBox is actually part of the structure. The InputBox object will become invisible again when it loses focus.

When it is shown, the InputBox object will receive a Rect object so that it will have access to the Rect object whose part is being edited. Listing 4-24 shows the InputBox class.

Listing 4-24: The InputBox Class

start example
 Option Explicit On Option Strict On Imports System Imports System.Windows.Forms Imports System.Collections Public Class InputBox : Inherits TextBox   'the interface or class that is being updated   Public rect As Rect   Public Property LineArrayList() As ArrayList     Get       Dim str() As String = Me.Lines       Dim al As New ArrayList()       Dim count As Integer = str.Length       Dim i As Integer       For i = 0 To count - 1         al.Add(str(i))       Next       Return al     End Get     Set(ByVal al As ArrayList)       Dim itemCount As Integer = al.Count       If itemCount = 0 Then         Me.Lines = Nothing       Else         Dim str(itemCount - 1) As String         Dim i As Integer         For i = 0 To itemCount - 1           str(i) = CType(al.Item(i), String)         Next         Me.Lines = str       End If     End Set   End Property End Class 
end example

The InputBox class adds the LineArrayList property to the TextBox class. This property enables an ArrayList to populate the Lines property. Assigning an ArrayList object to the InputBox object will iterate the ArrayList and create an array of string that will then be fed to the Lines property:

 Dim itemCount As Integer = al.Count If itemCount = 0 Then   Me.Lines = Nothing Else   Dim str(itemCount - 1) As String   Dim i As Integer   For i = 0 To itemCount - 1     str(i) = CType(al.Item(i), String)   Next   Me.Lines = str End If 

This property returns an ArrayList object containing each element in the Lines array of strings:

 Dim str() As String = Me.Lines Dim al As New ArrayList() Dim count As Integer = str.Length Dim i As Integer For i = 0 To count - 1   al.Add(str(i)) Next Return al 

The Line Class

The Line class represents a relationship line. There are four types of lines in this application, and they differ only in the graphical appearance—in other words, the end cap and the line style.

There are two possible ways of implementing it. The first is to create a base Line class with four child classes, each representing a line type. The base Line class will have a virtual method for drawing itself and will depend on polymorphism to draw the correct type of line. This solution is elegant, but the number of extra classes adds to the maintenance burden.

The second way is to have a variable that holds the type of the line and then use a Select Case block to draw the correct type of line. This project uses the latter because the only difference among the four types of lines is the type of Pen object to draw those lines.

A line always has two structures at its ends. When one of its structures is moved, the line will follow. In other words, the line will adjust its own position based on the positions of its structures. When first created, the adjustment is automatic, meaning that the line can connect to a structure at any of the structure's sides.

The user can drag a line manually, causing the mode of adjustment to change to manual. In this case, the line will only connect to the structure at that particular side.

The autoMove variable in the Line class indicates a line's adjustment mode.

You can see the Line class in the Line.vb file in the project's directory. To save space, the Line class source code is not printed here.

The Line Class's Constructors

The Line class has two constructors. The first constructor is used when the user draws a Line object. Its signature is as follows:

 Public Sub New(ByVal fromRect As Rect, ByVal toRect As Rect, _     ByVal index As Integer, ByVal lineType As ShapeType) 

Note that it accepts two Rect objects, fromRect and toRect. fromRect is the Rect object from which the line is drawn. toRect is the Rect object to which the line is drawn. It also accepts the index number and the line type. The line type is of type ShapeType enumeration.

This constructor first assigns the arguments to the class variables:

 Me.lineType = lineType Me.fromRect = fromRect Me.toRect = toRect Me.index = index 

It then wires the LocationChanged, HandleDestroyed, and Resize events of both Rect objects.

 AddHandler fromRect.LocationChanged, AddressOf rect_LocationChanged AddHandler toRect.LocationChanged, AddressOf rect_LocationChanged AddHandler fromRect.HandleDestroyed, AddressOf rect_HandleDestroyed AddHandler toRect.HandleDestroyed, AddressOf rect_HandleDestroyed AddHandler fromRect.Resize, AddressOf rect_Resize AddHandler toRect.Resize, AddressOf rect_Resize 

When one of its Rect objects is moved or resized, the Line object needs to readjust its position. Therefore, both the rect_LocationChanged and the rect_Resize event handlers call the ReadjustPosition method.

Next, the constructor constructs a Pen object used for drawing itself and readjusts its location by calling the ConstructPen and ReadjustLocation methods:

 ConstructPen() ReadjustLocation() 

The second constructor reconstructs the Line object from a LineMemento object. Its signature is as follows:

 Public Sub New(ByVal fromRect As Rect, ByVal toRect As Rect, _     ByVal memento As LineMemento) 

It calls the first constructor and then populates the Line object with the states from the LineMemento object.

The ConstructPen Method

This method constructs a Pen object based on the value of the lineType variable. The ConstructPen method is only called once—in other words, when the Line object is instantiated. The method contains a Select Case block such as the following:

 Select Case Me.lineType   Case ShapeType.Generalization     ...   Case ShapeType.Dependency     ...   Case ShapeType.Association     ...   Case ShapeType.Aggregation     ... End Select 

The ReadjustLocation Method

The ReadjustLocation method changes the value of the Line object's startPoint and endPoint. There are two types of adjustments: automatic and manual, which are determined by the value of autoMove. When adjustment is automatic, the ReadjustLocation method takes into account the relative positions of fromRect and toRect.

When adjustment is manual, the Line object "remembers" which side of fromRect and toRect it connects to and at which points on those sides. The Line class uses a private enumeration whichSide that defines the four sides of a Rect object:

 Private Enum whichSide As Integer   Top = 0   Bottom = 1   Left = 2   Right = 3 End Enum 

As in the automatic adjustment, the manual adjustment aims at updating the value of the Line object's start point and end point of both fromRect and toRect. This is the skeleton of the part that deals with the manual adjustment:

 'adjust the StartPoint Select Case fromRectWhichSide   Case whichSide.Bottom     ...   Case whichSide.Top     ...   Case whichSide.Left     ...   Case whichSide.Right     ... End Select 'adjust the EndPoint Select Case toRectWhichSide   Case whichSide.Bottom     ...   Case whichSide.Top     ...   Case whichSide.Left     ...   Case whichSide.Right     ... End Select 

The Draw Method

The Draw method draws the following.

  • The line connecting the start point and the end point

  • The three labels at the left, center, and right side of the line

  • The handles when the Line object is selected

Note that when the line type is ShapeType.Aggregation or ShapeType.Generalization, the Draw method needs to do the following extra work:

 If lineType = ShapeType.Aggregation Or _   lineType = ShapeType.Generalization Then   'create a new GraphicsPath so the following transform won't affect the   'arrow head   Dim gp As GraphicsPath = CType(arrowHeadGraphicPath.Clone, GraphicsPath)   Dim rotAngle As Single = GetRotationAngle()   g.TranslateTransform(EndPoint.X, EndPoint.Y)   g.RotateTransform(rotAngle + 270)   g.FillPath(Brushes.White, gp)   'FillPath will erase some of the graphics path   'redraw the arrow head   g.DrawPath(Pens.Black, gp)   g.ResetTransform() End If 

To understand why it needs to do the extra work, look at the part near the end points of an aggregation line and a generalization line in Figure 4-25 if the Draw method does not do this.


Figure 4-25: Imperfect aggregation line and generalization line

See how the lines go through the arrow heads? The "extra work" patches the arrow heads with a GraphicsPath object filled with the background color (in this case, white):

 g.FillPath(Brushes.White, gp) 

Because the patching can override some points of the arrow head, the arrow head needs to be redrawn with the foreground color (in this case, black):

 g.DrawPath(Pens.Black, gp) 

The TranslateTransform method of the Graphics class is called to move the origin to the end point:

 g.TranslateTransform(EndPoint.X, EndPoint.Y) 

The RotateTransform method rotates the GraphicsPath object to the correct angle. The correct rotation angle is returned by the GetRotationAngle method.

Finally, you reset the transformation by calling the ResetTransform method:

 g.ResetTransform() 

The GetMemento Method

The GetMemento method returns a LineMemento object containing the states of the Line that need to be persisted.

The Removed Event

The Line class raises the Removed event when one of its structures is removed. The DrawArea control captures this event.

The LineMemento Class

The LineMemento class represents a memento for a Line object (see Listing 4-25). Note that this class is marked with the <Serializable> attribute to make the LineMemento object serializable.

Listing 4-25: The LineMemento Class

start example
 Imports System Imports System.Drawing <Serializable()> Public Class LineMemento   Public index As Integer   Public lineType As ShapeType   Public fromRectIndex As Integer   Public toRectIndex As Integer   Public StartPoint As Point   Public EndPoint As Point   Public selected As Boolean   Public leftText As String   Public centerText As String   Public rightText As String   Public autoMove As Boolean   Public fromRectXRelPos As Integer   Public fromRectYRelPos As Integer   Public toRectXRelPos As Integer   Public toRectYRelPos As Integer   Public fromRectWhichSide As Integer   Public toRectWhichSide As Integer End Class 
end example

The PropertyForm Class

A form edits the labels attached to a line. This form is called the property form and is represented by the PropertyForm class. You can find the PropertyForm class in the PropertyForm.vb file in the project's directory; it is not printed here to save space.

Note

The user can display the property form by pressing the F4 key or by clicking Property Form from the View menu.

Figure 4-26 shows the property form.

click to expand
Figure 4-26: The property form

The PropertyForm class includes the PropertyChanged event, which is of type PropertyEventHandler. The PropertyEventHandler delegate is defined as follows:

 Public Delegate Sub PropertyEventHandler(ByVal sender As Object, _   ByVal e As EventArgs) 

The PropertyChanged event triggers every time a property is updated.

The DrawArea Class

The DrawArea class represents the draw area of the application. It derives from the System.Windows.Forms.Panel class. You can find the DrawArea class in the DrawArea.vb file in the project's directory. It is not printed here to save space.

The Form

The form is represented by the Form1 class, which is derived from the System.Windows.Forms.Form class. You can find it in the Form1.vb file in the project's directory. It is not printed here to save space.

Compiling and Running the Application

You can find all the classes used in the application in the UMLClassDiagram.vb file. To compile this application, follow these steps:

  1. Create a working directory.

  2. Copy all the .vb files to the working directory.

  3. Run the build.bat file to build the project.

The content of the build.bat file is as follows:

 vbc /t:library /r:System.dll Misc.vb vbc /t:library /r:System.dll,System.Drawing.dll,mscorlib.dll MathUtil.vb vbc /t:library /r:System.dll,System.Drawing.dll ClassRectMemento.vb vbc /t:library /r:System.dll,System.Drawing.dll InterfaceRectMemento.vb vbc /t:library /r:System.dll,System.Drawing.dll,  System.Windows.Forms.dll,Misc.dll,MathUtil.dll Rect.vb vbc /t:library /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll,  Misc.dll,MathUtil.dll,Rect.dll,ClassRectMemento.dll ClassRect.vb vbc /t:library /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll,  Misc.dll,MathUtil.dll,Rect.dll,InterfaceRectMemento.dll InterfaceRect.vb vbc /t:library /r:System.dll,System.Drawing.dll,Misc.dll LineMemento.vb vbc /t:library /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll,  Misc.dll,MathUtil.dll,Rect.dll,LineMemento.dll Line.vb vbc /t:library /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll,  Line.dll PropertyForm.vb vbc /t:library /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll,  Rect.dll InputBox.vb vbc /t:library /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll,  Misc.dll,MathUtil.dll,Rect.dll,ClassRect.dll,InterfaceRect.dll,  ClassRectMemento.dll,InterfaceRectMemento.dll,Line.dll,  LineMemento.dll,InputBox.dll DrawArea.vb vbc /t:winexe /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll,  Misc.dll,MathUtil.dll,Rect.dll,ClassRect.dll,InterfaceRect.dll,  ClassRectMemento.dll,InterfaceRectMemento.dll,Line.dll,  LineMemento.dll,InputBox.dll,DrawArea.dll,PropertyForm.dll Form1.vb 

To run the application, type form1.exe in the command prompt. Note that the images directory must be in the directory where the form1.exe file resides. You should see the screenshot as displayed in Figure 4-27.

click to expand
Figure 4-27: The UML class diagram editor




Real World. NET Applications
Real-World .NET Applications
ISBN: 1590590821
EAN: 2147483647
Year: 2005
Pages: 82

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net