VBA -- Creating A Complex Application


Note: Portions of the code used in this article were originally contributed by Mark Stefanchuk and Graham Steele and are used by permission.

In this example, framework is set up for an application that places different doors. The doors then can be linked to Tag Data that can be later referenced for easy identification. The application is organized into modules (i.e. ModRun, FmDoorMgr, clsDoor, clsRollUpDoor, clsStallDoor, clsRegularDoor, and ManipulateCell) that match the application items, so you can build it as one project made of components that can be reused in other projects.

The first component to look at is the user interface, which is contained in the FmDoorMgr form class and allows for the selection of the type and size of door to place and the options for the door (e.g. double, reverse, etc.) This dialog opens when the command is started, so default settings must be specified to allow the user to be in placement mode if they do not want to change any setting.

clsDoor is the generic door object that handles the different doors and associated door information. The associated door information contains the fire rating and the door ID number and can be optionally placed as Tag Data. The application creates a door, then the door geometry, and finally the tag to add to the door geometry. By putting these steps in separate classes, changes can be made to any one of them without affecting the rest of the application workflow. An option is to add a database connection so that the information can also be stored in an external database, but that option will not be covered in this article. In clsDoor, the door geometry is created using the method CreateDoor, which accepts a door type and other parameters as input to determine the door to place. When the door is finally placed, the Tag Data is added using the TagDoor method. The CreateDoor method uses a select/case structure to create the proper door that has been requested.

Option Explicit

Public Enum DoorType
itypicaldoor = 1
iRollUpDoor = 2
iStallDoor = 3
End Enum

Private pDoor As Object 'this is the door.
Private pTag As clsDoorTags 'this is the tagging for the door information.

Private m_DoorType As DoorType
'Generic create method to allow for creating any door
Public Sub CreateDoor(iDoorType As Integer, dblDoorSize As Double, _
bReverse As Boolean, bDoubleDoor As Boolean, bTagInfo As Boolean)
'this will call the specific door creation method.
Select Case iDoorType
Case 3
    Set pDoor = New clsStallDoor
    pDoor.CreateDoor dblDoorSize
Case 2
    Set pDoor = New clsRollUpDoor
    pDoor.CreateDoor dblDoorSize
Case Else
    Set pDoor = New clsRegularDoor
    pDoor.CreateDoor dblDoorSize, bDoubleDoor
End Select

End Sub

Public Sub TagDoor(iFireRating As Integer, iDoorID As Integer)

Set pTag = New clsDoorTags

pTag.CreateDoorTags iFireRating, iDoorID, pDoor

End Sub
Public Property Get door() As Object
Set door = pDoor
End Property
Public Property Get TagInfo() As clsDoorTags
Set TagInfo = pTag
End Property

clsDoorTag isolates the Tag Data from the door. The door's Tag Data is added through the CreateDoorTags method. The next thing that is added is the ability to uniquely identify the doors. In this particular application, that is added in a utility function that scans the model refs and finds the highest used ID number. A utility module is used since the same method is needed for both the GUI and the placement code.

Option Explicit

Dim iFireRating As Integer
Dim iDoorID As Integer

Public Property Let FireRating(iRating As Integer)
iFireRating = iRating
End Property
Public Property Get FireRating() As Integer
FireRating = iFireRating
End Property

Public Property Let DoorID(id As Integer)
iDoorID = id
End Property

Public Property Get DoorID() As Integer
DoorID = iDoorID
End Property
Public Function CreateDoorTags(iFireRating As Integer, iDoorID As Integer, pDoor As Object) As Boolean
Dim strName As String
Dim oTagSets As TagSets
Dim oTagSet As TagSet
Dim oTagDefns As TagDefinitions
Dim oTagDefn As TagDefinition
Dim oTagElement As TagElement
strName = "Door Tags"
On Error Resume Next
'this will create the tag def if it is not in the file
CheckForTagDefn True

Set oTagSets = ActiveDesignFile.TagSets
Set oTagSet = oTagSets(strName)
'if the tag does not exist this will throw an error
Set oTagDefns = oTagSet.TagDefinitions

Set oTagDefn = oTagDefns("Fire Rating")
'this adds the tag to the element
Set oTagElement = pDoor.door.AddTag(oTagDefn)
oTagElement.Value = iFireRating
'since the addTag writes to file the otagelement needs to be rewritten.
oTagElement.Rewrite
oTagElement.Redraw
Set oTagDefn = oTagDefns("Door ID")
Set oTagElement = pDoor.door.AddTag(oTagDefn)
oTagElement.Value = iDoorID
oTagElement.Move Point3dFromXY(0, -ActiveSettings.TextStyle.Height * 2)
oTagElement.Rewrite
oTagElement.Redraw
CreateDoorTags = True
End Function
Private Sub CheckForTagDefn(bCreateIfMissing As Boolean)
Dim oTagSet As TagSet
Dim oTagDefn As TagDefinition
Dim strName As String
Dim oTagSets As TagSets
On Error Resume Next

strName = "Door Tags"
Set oTagSets = ActiveDesignFile.TagSets
Set oTagSet = oTagSets(strName)
If bCreateIfMissing And oTagSet Is Nothing Then
    Dim oTagDef As TagDefinition
    Set oTagSet = oTagSets.Add(strName)
    
    Set oTagDef = oTagSet.TagDefinitions.Add("Fire Rating", msdTagTypeLongInteger)
    oTagDef.IsHidden = False
    oTagDef.DefaultValue = ""
    
    Set oTagDef = oTagSet.TagDefinitions.Add("Door ID", msdTagTypeLongInteger)
    oTagDef.IsHidden = False
    oTagDef.DefaultValue = ""
End If

errorHandle:

End Sub

The next part to look at is the set of classes that make up the different doors. By defining the doors in classes, the code can create a specific door without referring to the specific door type. The benefit is that the application can be enhanced or extended by simply adding door types as new classes. The structure of the class is to use a cell for the graphics. The object can also be programmed to contain other information that can be added as the application evolves. Each class contains a CreateDoor method that generates the graphics for the door. The graphics are identified in a member variable m_DoorType, which is private to the class and exposed only as a get property. Using this approach, the graphics are readable but cannot be set or changed from outside the object and the user class cannot assign graphics to the object. The object also includes a flag to determine if door is to be placed reversed or as a double door.

Option Explicit
Private m_TypicalDoor As CellElement
Private m_reverse As Boolean
Private m_tagNumber As Integer
Private m_swingDoor As Boolean
Public Property Get Swing() As Boolean
Swing = m_swingDoor
End Property
Public Property Let Swing(s As Boolean)
m_swingDoor = s
End Property
Public Property Get reverse() As Boolean
reverse = m_reverse
End Property
Public Property Let reverse(rev As Boolean)
m_reverse = rev
End Property
Public Property Get door() As CellElement
Set door = m_TypicalDoor
End Property
Private Property Set door(pDoor As CellElement)
Set m_TypicalDoor = pDoor
End Property
Public Property Get tagNumber() As Integer
tagNumber = m_tagNumber
End Property
Public Property Let tagNumber(t As Integer)
m_tagNumber = t
End Property

Function CreateDoor(DoorSize As Double, Optional bDblDoor As Boolean) As CellElement

...Code for door creation here -- this will differ based on the door geometry you wish to create...

The manipulation class is an implementation of an IPrimitiveCommandEvents class that acts as the placement code. By implementing this interface, the class is called for Data Points and Resets, which are typical events for element placement in MicroStation. The placement code only needs to determine the door type being placed once -- after that all the code is common between the types. This makes the code clearer and simpler since there are not multiple cases that need to be implemented and the code is only written once. In this application, the dynamics allow the door to be flipped by pressing Reset. Two methods of interest in this class are the dynamics and the datapoint methods. In the dynamics method, a door is created on-the-fly and is presented on the cursor. If the user has already established the anchor point the door is then put in a rotation mode to allow the door to be placed at an angle determined by a second data point. In the datapoint method, the first action is to collect the anchor point. Once the door is anchored users can then rotate the door. If they press Reset, the door is mirrored, allowing for a more flexible placement process. Again, the focus of this class is on placing graphics and not on what is being placed.


Option Explicit

Implements IPrimitiveCommandEvents
Dim m_atPoints(0 To 1) As Point3d
Dim DataPoints As Integer
Dim Reset As Boolean
Private m_nPoints As Integer
Dim InsertPoint As Boolean
Dim reverse As String

Private Sub IPrimitiveCommandEvents_Cleanup()
CommandState.StopDynamics
Unload FmDoorMgr
'CommandState.StartDefaultCommand
End Sub



Private Sub IPrimitiveCommandEvents_DataPoint(Point As Point3d, ByVal View As View)
'Dim oCell As CellElement
Dim oDrCell As CellElement
Dim oDoor As clsDoor
Dim bDblDoor As Boolean
If FmDoorMgr.TxtDbleDoorSize > 0 Then
bDblDoor = True
Else
bDblDoor = False
End If

    If DataPoints = 0 Then 'user has entered Startpoint of the line segment
        Dim ZAngle As Double
        m_atPoints(0) = Point
        InsertPoint = True
        DataPoints = 1
       
        CommandState.StartDynamics
        ShowPrompt "Rotate point"
    Else
'    CommandState.StopDynamics
        Set oDoor = New clsDoor
        m_atPoints(1) = Point
            If FmDoorMgr.OptStallDoor.Value = True Then
                oDoor.CreateDoor 3, FmDoorMgr.TxtDoorSize, FmDoorMgr.cbReverse, bDblDoor, False
            ElseIf FmDoorMgr.OptRollUp.Value = True Then
                oDoor.CreateDoor 2, FmDoorMgr.TxtDoorSize, FmDoorMgr.cbReverse, bDblDoor, False
            Else
                oDoor.CreateDoor 1, FmDoorMgr.TxtDoorSize, FmDoorMgr.cbReverse, bDblDoor, False
            End If
       
        oDoor.door.reverse = FmDoorMgr.cbReverse
        'Transform the elemenet
        oDoor.door.door.Transform Transform3dFromPoint3d(m_atPoints(0))
        'Calculate the angle and rotate the cell
        ZAngle = Atn2(m_atPoints(0).Y - m_atPoints(1).Y, m_atPoints(0).X - m_atPoints(1).X)
        oDoor.door.door.Rotate m_atPoints(0), 0, 0, ZAngle
       
       
        ActiveModelReference.AddElement oDoor.door.door

        'make this part of the door object.
            If FmDoorMgr.TxtDrNbr.Text <> "" Then
               ' CreateDoorNumber Point, ZAngle, reverse
               oDoor.TagDoor 1, CInt(FmDoorMgr.TxtDrNbr)
               'increment the door number here?
               FmDoorMgr.TxtDrNbr = FmDoorMgr.TxtDrNbr + 1
            End If
       
        oDoor.door.door.Redraw msdDrawingModeNormal
        'Set the flags to initial state
        InsertPoint = False
        Reset = False
        DataPoints = 0
    End If
  Reset = False
End Sub

Private Function CreateCellForDynamics() As Object
Dim pDoor As Object
If FmDoorMgr.OptStallDoor.Value = True Then
    If Reset = True Then
        Set pDoor = New clsStallDoor
    Else
        Set pDoor = New clsStallDoor
     End If
ElseIf FmDoorMgr.OptRollUp.Value = True Then
     If Reset = True Then
        Set pDoor = New clsRollUpDoor
    Else
        Set pDoor = New clsRollUpDoor
     End If
Else
    If Reset = True Then
        Set pDoor = New clsRegularDoor
    Else
        Set pDoor = New clsRegularDoor
    End If
End If

pDoor.reverse = FmDoorMgr.cbReverse
pDoor.CreateDoor CDbl(FmDoorMgr.TxtDoorSize.Text), CBool(FmDoorMgr.TxtDbleDoorSize.Text)
Set CreateCellForDynamics = pDoor
End Function
Private Sub IPrimitiveCommandEvents_Dynamics(Point As Point3d, ByVal View As View, ByVal DrawMode As MsdDrawingMode)
Dim oCell As CellElement
Dim ZAngle As Double
Dim pDoor As Object

Set pDoor = CreateCellForDynamics

If InsertPoint = True Then
    Dim RotationMatrix As Matrix3d
    m_atPoints(1) = Point
    ZAngle = Atn2(m_atPoints(0).Y - m_atPoints(1).Y, m_atPoints(0).X - m_atPoints(1).X)
   
    pDoor.door.Transform Transform3dFromPoint3d(m_atPoints(0))
    pDoor.door.Rotate m_atPoints(0), 0, 0, ZAngle
Else
    pDoor.door.Transform Transform3dFromPoint3d(Point)
End If
'this is for the mirroring
If Reset Then
    pDoor.door.Mirror pDoor.door.Origin, Point
End If

pDoor.door.Redraw DrawMode

Set pDoor = Nothing

End Sub

Private Sub IPrimitiveCommandEvents_Keyin(ByVal KeyIn As String)

End Sub

Private Sub IPrimitiveCommandEvents_Reset()
    If Reset = True Then
        CommandState.StopDynamics
        CommandState.StartDefaultCommand
        RedrawAllViews
    Else
        Reset = True
        CommandState.StartDynamics
    End If
End Sub

Private Sub IPrimitiveCommandEvents_Start()
FmDoorMgr.Show
InsertPoint = False
'This starts accurdraw in the iprimative event.
CommandState.EnableAccuSnap
CommandState.StartDynamics
End Sub

One of the most powerful methods of application development is to divide and conquer. In this application, the problem is divided into multiple classes, each handling one specific task. The form is used only for collecting user information, the ManipulateCell class is used only to place the graphics, and the clsDoor object is used to isolate the door from the rest of the application.