Advanced – use the Fixes optional dialog
The next step in the development of Standards Checker extensions is to add the capability to fix issues as they are found during the check process. To do this, the checker macro needs to call the ShowCheckerErrorWithFixOptions method which will call the GetFixDetails method.
This method requires the macro to fill a list of possible corrections for the user to pick from. These “fixes” are then presented to the user in the Standards Checker user interface. When the user has selected one of the fixes, it is sent back to the macro which then applies the fix to the element. Once the GetFixDetails method has returned the value of the ShowCheckerErrorWithFixOptions method, it is checked and one of the possible actions is taken. The results are then updated in the report using the StandardsCheckerReport class.
Building Custom Standards Checker Applications
One thing that is not apparent about the Standards Checker is the ability to extend the functionality to check more than just symbology or level definitions. It is a framework for processing design files. Application developers can use it to build custom plug‐in solutions to extend the capabilities.
An example of this functionality is the ability to read a corporate standard in a common file that is read at check time. Since symbology is more complex, only one type of information can be on one level. Custom interpretation of the information is required.
To build a custom plug‐in, start by creating a new project. In this project, add a new module. This module will be the entry point for the macro.
The next step is to insert a new class. In the new class, add the line to implement the IstandardsChecker interface.
The next step is to simply add the required method and property signatures for the interface. Now that the class to handle the standard checking is in place, add a subroutine in the module to load the class into the standard checker. An
important note is that the plug‐in can be loaded multiple times. To prevent this, the macro should unload the class before trying to load the class. To move the custom plug‐in to the top of the list, use a high priority number (the second parameter).
Option Explicit
Public oSettingsCollection As SettingCollection Public stdCheckerApp As clsStandardsCheckerImpl Private oSC As IstandardsChecker
Sub OnProjectLoad() AddStdCheckerApp End Sub
Sub AddStdCheckerApp()
‘
' There is nothing to prevent this program from adding 2 standards
‘ checkers. To prevent that from happening, call RemoveAttachmentsChecker
‘ before adding the checker.
‘
RemoveStdCheckerApp
Set oSettingsCollection = New SettingCollection oSettingsCollection.init
Set stdCheckerApp = New clsStandardsCheckerImpl
StandardsCheckerController.AddStandardsChecker stdCheckerApp, 1000 End Sub
Sub RemoveStdCheckerApp()
If Not stdCheckerApp Is Nothing Then
StandardsCheckerController.RemoveStandardsChecker stdCheckerApp Set stdCheckerApp = Nothing
End Sub
By implementing the interface, the class code will appear as follows.
Implements IStandardsChecker
Private Property Get IStandardsChecker_CallForEachModel() As Boolean End Property
Private Sub IStandardsChecker_CreateSettings() End Sub
Building Custom Standards Checker Applications
Private Sub IStandardsChecker_DeleteSettings() End Sub
Private Property Get IStandardsChecker_Description() As String End Property
Private Property Get IStandardsChecker_DialogString() As String End Property
Private Sub IStandardsChecker_EditSettings(ByVal IsReadOnly As Boolean) End Sub
Private Property Get IStandardsChecker_FoundSettings() As Boolean End Property
Private Sub IStandardsChecker_GetFixDetail(Fixes() As String, _ ByVal SelectedFix As Long, _
FixPropertiesLabel As String, _ FixProperties() As String) End Sub
Private Property Get IStandardsChecker_HasSettings() As Boolean End Property
Private Property Get IStandardsChecker_IdentityString() As String End Property
Private Sub IStandardsChecker_RunCheck _ (ByVal ModelToCheck As ModelReference, _ ByVal FirstModel As Boolean, _
ByVal Options As Long) End Sub
Private Property Get IStandardsChecker_VersionString() As String End Property
The macro control properties and methods to note here are HasSettings,
EditSettings, FoundSetting, CreateSetting, and DeleteSetting. The
HasSettings method is called by the interface to determine if the macro has some configurable settings. This will activate the settings button on the user interface. When the user selects the settings button, the EditSettings method in the plug‐in will be invoked. Plug‐in settings are stored as a settings element in the DGN library. If the plug‐in wants to store its settings in the design file, the
CreateSettings method is invoked by the standards macro.
The DeleteSettings method is called to let the plug‐in delete the settings element. The FoundSettings property is used to tell the macro that the plug‐in has settings and is capable of running. For a macro to work properly, it must set
FoundSettings to True so that it will be enabled on the user interface. Another property that a plug‐in should set is CallForEachModel. This tells the macro that the plug‐in should be called for each model in the design file.
The RunCheck method is called when the macro is invoked to check the elements.
When the macro invokes the RunCheck method it will pass in the model that is being checked, some additional information such as if it is the first (or active) model, and some options that affect the operation of the plug‐in. In the RunCheck the plug‐in will do most of its work. The plug‐in will need to iterate through the elements it will check and, if necessary, invoke the user interface. To invoke the macro user interface, the plug‐in will call the StandardsCheckerController method, ShowCheckerError method, or ShowCheckerErrorWithFixOptions to interact with the user.
ShowCheckerErrorWithFixOptions requires some set up by the macro. The plug‐in will provide the options that the user can choose from if they want to fix properties for the TotalProblems and FixedProblems that will be used in the user interface. If the plug‐in wants to add information to the XML report, it uses the StandardCheckerReport.AddProblem method. The last parameter for this method lets the report know if the plug‐in has fixed the element. The RunCheck is the heart of the plug‐in and, as such, will be the method that requires the most work when implementing a plug‐in.
The other methods provide some information about the macro to the standard interface. The Overstraining property lets the macro add information about the
Building Custom Standards Checker Applications
plug‐ins that are used in processing the file to the report. The IdentityString property is used to provide a unique name for the plug‐in. The DialogString property is applied to the main dialog that the user will select from in the macro.
The Description property is used in report generation from the macro.
To demonstrate this process, this sample macro will use the RunCheck method to process each element in the file. The settings for this plug‐in will determine the collection of rules to apply. As each model is passed into the RunCheck method, the plug‐in will iterate through the elements of that model.
Private Sub IStandardsChecker_RunCheck _ (ByVal ModelToCheck As ModelReference, _ ByVal FirstModel As Boolean, _
ByVal Options As Long)
Dim oEnum As ElementEnumerator Dim oCheckElement As Element
If oSettingsCollection.GetSettings.Count = 0 Then MsgBox "Rerun and Pick Settings file first"
Exit Sub End If
Set oEnum = ModelToCheck.Scan
Do While oEnum.MoveNext
checkSingleElement oEnum.Current Loop
End Sub
Each element is passed into the CheckSingleElement method that is defined in the plug‐in. The CheckSingleElement method will examine the element and, in the case of a simple element, it will apply the check to the element. If the element is complex it will get the components and check them recursively. If the element passes the check, it allows the StandardsChecker to continue processing.
Sub checkSingleElement(ocheckel As Element) Dim oSubEnum As ElementEnumerator
Dim oSubEl As Element Dim oSetting As Setting
If ocheckel.IsComplexElement Then
Set oSubEnum = ocheckel.AsComplexElement.GetSubElements Do While oSubEnum.MoveNext
checkSingleElement oSubEnum.Current Loop
Else
If ocheckel.IsGraphical And _
ocheckel.Class = msdElementClassPrimary Then Set oSetting = New Setting
oSetting.InitFromElement ocheckel
If oSettingsCollection.check(oSetting) = False Then ReportBadElement ocheckel
End If End If
End If End Sub
If the element fails the check it will be passed to the ReportBadElement method that the plug‐in defines. The ReportBadElement method will invoke the
StandardsCheckerShowErrorWithFixOptions method.
To call this method, the plug‐in will build the fixes array from the possible combinations that the element uses. The plug‐in uses the GetFixes method to generate information about the possible fixes.
' ShowCheckerErrorWithFixOptions calls this to get the data to display ' in the bottom section of the dialog. It also calls this everytime the ' user selects a different row in the list of fixes.
'
Private Sub IStandardsChecker_GetFixDetail _ (Fixes() As String, _
ByVal selectedFix As Long, _ FixPropertiesLabel As String, _ FixProperties() As String)
FixPropertiesLabel = "VBA Example Fix Properties"
Building Custom Standards Checker Applications
Dim oSetting As Setting Dim i As Integer
i = oSettingsCollection.GetSettings.Count
ReDim FixProperties(1 To i, 0 To 2)
For i = 1 To oSettingsCollection.GetSettings.Count FixProperties(i, 0) = "Symbology Settings"
FixProperties(i, 1) = oSettingsCollection.GetSettings.Item(i).ToString FixProperties(i, 2) = oSettingsCollection.GetSettings(i).description Next i
End Sub
When the user selects from the options on the StandardsChecker macro
interface, the selectedFix option is set to one of the available options. The plug‐
in uses this value to determine the process path. The
msdStandardsCheckerReplaceChoiceSkip is set when the plug‐in should simply skip the element.
The msdStandardsCheckerReplaceChoiceMarkIgnored option lets the plug‐in mark the element as ignored and the report can have these added so the user can revisit the element. The msdStandardsCheckerReplaceChoiceFix option tells the plug‐in that the element can be fixed according to the one of the fixes choices that the user has selected. The plug‐in can then take the corrective action and use the AddProblem method to set the element as fixed. Once the element has been processed the plug‐in will increment the TotalProblems and continue
processing.
Private Sub ReportBadElement(oelm As Element) Dim scc As StandardsCheckerController Dim rpt As StandardsCheckerReport
Dim response As MsdStandardsCheckerReplaceChoice Dim strDescr As String
Dim opts As MsdStandardsCheckerReplaceOptions
Dim handlerChoice As MsdStandardsCheckerReplaceChoice Dim selFix As Long
Dim columnLabels(0 To 1) As String Dim Fixes() As String
Dim oCurrSetting As New Setting Dim i As Integer
Dim scp As StandardsCheckerProblem
Set currElement = oelm
oCurrSetting.InitFromElement oelm
'StandardsCheckerController.ShowCheckerStatus "Example Status Message"
columnLabels(0) = "Symbology Options"
columnLabels(1) = ""
i = oSettingsCollection.GetSettings.Count
'set up the list of possible fixes for this element ReDim Fixes(0 To i, 0 To 1)
Fixes(0, 0) = oCurrSetting.ToString: Fixes(0, 1) = "Current Settings"
For i = 1 To oSettingsCollection.GetSettings.Count
Fixes(i, 0) = oSettingsCollection.GetSettings.Item(i).ToString Fixes(i, 1) = _
oSettingsCollection.GetSettings.Item(i).description Next i
strDescr = "Bad Element: " & DLongToString(oelm.ID) & _ ElmToInfoString(oelm)
Set scc = StandardsCheckerController
opts = msdStandardsCheckerReplaceOptionCanFix Or _ msdStandardsCheckerReplaceOptionCanIgnore
scc.ShowCheckerErrorWithFixOptions response, selFix, _ strDescr, _
"Fixes List Box Label", _ columnLabels, Fixes, 0, _ opts, _
False
Set rpt = scc.Report
If response = msdStandardsCheckerReplaceChoiceSkip Then Set scp = rpt.AddProblem(strDescr, "My Check", False)
' Record the ElementID because it is the only property of an Attachment
Building Custom Standards Checker Applications
‘ that cannot change. If someone writes a program that processes ' the problem report, they can use the ElementID to be certain the ' program accesses the same attachment that the report refers to.
scp.AddElementID stdCheckerApp.currElement.ID
scp.AddStandard "My Element Checker App", m_libraryID scp.AddVariance "My Symbology", "", oCurrSetting.ToString End If
If response = msdStandardsCheckerReplaceChoiceFix Then Dim aSetting As Setting
Set scp = rpt.AddProblem(strDescr, "My Check", True) 'the last parameter will put the check in the fixed column scp.AddStandard "My Element Checker App", m_libraryID scp.AddAction "Fixed Element Symbology"
scp.AddElementID stdCheckerApp.currElement.ID If selFix > 0 Then
Set aSetting = oSettingsCollection.GetSettings(selFix) Set oelm.Level = _
ActiveDesignFile.Levels.Find(aSetting.Level) oelm.Color = aSetting.Color
oelm.LineWeight = aSetting.Weight End If
oelm.Redraw oelm.Rewrite
scc.FixedProblems = scc.FixedProblems + 1 End If
If response = msdStandardsCheckerReplaceChoiceMarkIgnored Then Set scp = rpt.AddIgnoredProblem("Element ", strDescr, _
"My Check", False)
scp.AddAction "Fixed Element Symbology"
scp.AddElementID stdCheckerApp.currElement.ID
scp.AddStandard "My Element Checker App", m_libraryID scp.AddVariance "My Symbology", "", oCurrSetting.ToString scc.IgnoredProblems = scc.IgnoredProblems + 1
End If
' If the user entered Cancel, tell RunCheck to abort If response = msdStandardsCheckerReplaceChoiceAbort Then
stdCheckerApp.m_aborted = True
scc.TotalProblems = scc.TotalProblems + 1
End Sub
The plug‐in can use the AddedCheckerToStandardsCheckerApps method to add information to the report and call the AddLibraryToCheckerApp to add a node to the report. The report’s AddStandardToLibrary method is used to add the information to the XML data.
Private Sub IStandardsChecker_AddedCheckerToStandardsCheckerApps _ (ByVal ApplicationXMLNode As Object)
Dim rpt As StandardsCheckerReport
' If the program declares these as IXMLDOMNode, then the program ' can add custom XML data to the report. If the program declares them ' as IXMLDOMNode then it must also set a reference to Microsoft XML ' v4.0.
Dim oLibraryNode As Object ' or IXMLDOMNode
Set rpt = StandardsCheckerController.Report
' m_libraryID is output from AddLibraryToCheckerApp.
' It is used later as input to AddStandard
Set oLibraryNode = rpt.AddLibraryToCheckerApp(ApplicationXMLNode, _ StandardsCheckerController.SettingsFile, m_libraryID)
rpt.AddStandardToLibrary oLibraryNode, "Element Checker", "Elements"
End Sub
The framework that is provided in the IStandardsChecker interface allows the plug‐in developer to interact with the StandardsChecker user interface and build a seamless extension. The class that implements the IstandardChecker interface does not need to be developed in VBA — it can be defined in any language that supports COM.
The StandardsChecker keeps a list of COM objects that it will invoke. The configuration variable MS_STANDARDCHECKER_APP can be used to automatically load the plug‐in. If the file name ends in .mvba, it will automatically be added to
Building Custom Standards Checker Applications
the list of applications that are loaded when the StandardsChecker is invoked by the user. There are many problems for plug‐ins to solve such as checking for missing references, etc.
Exercise: Implement a check
1 Implement the simple missing reference checker.
Questions
1 What are the keys to implementing a StandardsChecker extension?
2 In an implementation of the IStandardsChecker interface, what is the purpose of calling the HasSettings method?
3 True or False: A class implementing an IStandardsChecker interface need not be developed in VBA — it can be defined in any language that supports COM.