Creating an Add-in: an Overview

Introduction

This overview illustrates how to create an add-in by annotating an add-in, called AutoDist,  written in Visual Basic. If you choose to install sample code, the full program is available in Samples\Addins folder; the Visual Basic Project file is AutoDistLib.vbp.

Although simple, the AutoDist add-in is useful. By default, MSBNx nodes are initially created with no distribution. AutoDist changes the default; it adds a distribution to new nodes and to nodes with new states. Obviously, this can be accomplished using standard options in MSBNX, but this simple task provides a complete example add-in scenario.

Most Add-ins watch for changes in a model via the model's events. However, an add-in is not allowed to edit a model while handling a model event. This sample code shows how AutoDist avoids the problem. Although AutoDist never edits a model when handling model events; it does add the model to a private "to do" list. Later, when it gets a document event saying that editing is now allowed to change the model, it processes the items on its list.

Getting Started

To create an add-in in Visual Basic, first, create a new Visual Basic project of type "ActiveX DLL". (Other ActiveX project types may work, too).

Next, add-ins must add the following references:

References

Binary

Purpose

MSBNx

msbnx.exe

Primary application that implements the document interfaces.

MSBN3 1.0 Type Library

msbn3.dll

Automation library for control of belief networks.

DTAS Menu (rev 2) Type Library

dtasmnu2.dll

Pop-up menu helper library.

In the new project, create a new class module. In the sample, the project is called AutoDistLib.vbp and the class module is called AutoDist.cls.

Contracting to Implement the IDocAddIn Interface

Every add-in must contract or promise to implement the MsbnXApp.IDocAddIn interface. In Visual Basic this is done by adding a "Implements" line.

Option Explicit

Implements MsbnXApp.IDocAddIn
' This is an add-in that adds a distribution to all new nodes.

Remembering the MSBNx Document and MSBN3 Model

An add-in will typically want remember the MSBNx document (DocIface object) to which it is attached. Also, it will usually want to remember the model to which the document is attached. It can remember these objects by assigning them to private variables. Here is AutoDist's definition of those variables:

Private WithEvents m_DocIface As DocIface   ' My view of MSBNx document
Private WithEvents m_Model As Model ' My view of the model

The "WithEvents" syntax says that the add-in will watch for events from the two objects.

Other Private Variables

The AutoDist add-in has other private variables. It uses the m_rgNodes variable to keep a queue of nodes to check later. This is its "to-do" list. Variable m_distenum is the current default distribution type.

Private m_rgNodes As Collection             ' A list of nodes on which to work
Private m_distenum As MSBN3Lib.DISTENUM

Initialize and Terminate

The AutoDist add-in uses the Class_Initialize method to initialize one of its private variables. A Class_Terminate method is also available but is not used to AutoDist.

Private Sub Class_Initialize()
m_distenum = deCondSparse
End Sub

Implementing the IDocAddIn Interface

Every add-in must implement the IDocAddIn interface by providing code for its DocIface property. After MSBNx creates an add-in, it sets this property to the document to which the add-in is attached.

The MSBNx application can also set this property to Nothing. That is its way of telling the add-in that it is no longer attached to a document.

Here is AutoDist's code:

'Remember the document to which I am attached
Public Property Set IDocAddIn_DocIface(aDocIFace As DocIface)
Set m_DocIface = aDocIFace
If m_DocIface Is Nothing Then
m_DocIface_Model Nothing
Else
m_DocIface_Model m_DocIface.Model
End If
End Property

The AutoDist add-in first remembers its document. If the document is not Nothing, it looks up the document's model by accessing the m_DocIface Model property. It remembers the model via the m_DocIface_Model function/event handler. (The next section of this overview shows m_DocIface_Model function.)

Watching for Model Changes

Whenever a document is bound to a model or unbound from a model, it raises its Model event. A document also fires this event when it changes from one model to another, for example, because of an undo operation.

To monitor these changes, an add-in should monitor the Model event with an event handler. Here is AutoDist's event handler:

'Remember the model to which I am attached
Private Sub m_DocIface_Model(Model As MSBN3Lib.Model)
Set m_Model = Model
If m_Model Is Nothing Then
Set m_rgNodes = Nothing
Else
Set m_rgNodes = New Collection
End If
End Sub

This code first remembers the model. If the model is Nothing, it destroys m_rgNodes, its work queue. Otherwise, it creates a new work queue.

Death of a Document

When a document is about to die, MSBNx can tell an add-in the news via two mechanism. MSBNx can set the add-in's IDocface property to Nothing, or MSBNx can make the document can fire a Remove event. (In the future, these two method may be reduced to one method.)

Here is the AutoDist's  event handler for Remove.

' Tells me that I'll be shutdown in a moment
Private Sub IDocAddIn_DocIface_Remove()
Set IDocAddIn_DocIface = Nothing
End Sub

Watching the Model and To-Do Lists

Most add-ins will watch for some model events. Add-ins are prohibited from editing the model while handling a model event. This prohibition stops an infinite regression of events.

The AutoDist add-in watches for two model events: ModelNodesAdd, which tells it when a new node has been added to the model, and NodeStatesAdd, which tells it when new states have been added to a node. AutoDist wants to add any needed distributions to nodes mentioned in these events. Because it is prohibited from editing the model now, it instead adds the nodes to a private to-do list.

' Tells me that a node has been added so I need to add it my to-do list
Private Sub m_Model_ModelNodesAdd(ByVal Node As Object)
m_rgNodes.Add Node
End Sub
' Tells me that the # of states has increased.
Private Sub m_Model_NodeStatesAdd(ByVal Node As Object, ByVal state As Object)
m_rgNodes.Add Node
End Sub

Timer Event and Model Editing

The document object fires a Timer event. When this event fires, the add-in is allowed to edit the model.

When the AutoDist add-in sees the Timer event, it goes through the nodes in its to-do list and tries to add a distribution to them. Note that other add-ins may have already edited the model. That means that AutoDist can not assume that the nodes on its to-do list have no distribution. Indeed, it can't even assume that the nodes still exist.

' Tells me I can do some work on the model
Private Sub m_DocIface_Timer()
If m_rgNodes Is Nothing Then
Exit Sub
End If

Dim aNode As MSBN3Lib.Node
Dim iNode As Long
For Each aNode In m_rgNodes
' check that it is still there
If m_Model.ModelNodes.ExistingKey(aNode) Then
If aNode.States.Count > 0 And aNode.Dist Is Nothing Then
'If add-ing a distribution causes an error, just go to the next node.
On Error GoTo SkipIt
aNode.AddDist m_distenum
SkipIt:
On Error GoTo 0
End If
End If
Next aNode
Set m_rgNodes = New Collection
End Sub

Editing Popup Menus

When a user right-clicks on a document, a popup menu appears. The m_DocIface_PopupBefore event gives the add-in a chance to edit the popup before the user sees it. The event provides a popup menu object and the location on the document that was clicked. The possible locations are PopUpPlaceNode, PopUpPlaceCanvas, and PopUpPlaceArc. (The locations are defined in the MSBNX enumeration called PopUpPlace.)

Here is AutoDist's code:

' A popup is about to appear on a document. Add our contribution
Private Sub m_DocIface_PopupBefore(PopUp As Object, PopUpPlace As PopUpPlace)
Dim aPopup As DTASMENULibCtl.DtMenu
Set aPopup = PopUp

If PopUpPlaceCanvas = PopUpPlace Then
aPopup.AddItem "|"
PopUpAdd aPopup, "Default Dist: Sparse", "DefaultDistSparse"
PopUpAdd aPopup, "Default Dist: CI", "DefaultDistCI"
End If
End Sub
'===========================================
' This code could be useful to any add-in.
Private Sub PopUpAdd(aPopup As DTASMENULibCtl.DtMenu, sMenuString As String, sFunction As String)
Dim vMe As Variant
Set vMe = Me
aPopup.Reference(aPopup.AddItem(sMenuString)) = Array(vMe, sFunction)
End Sub

AutoDist defines a private function, PopUpAdd, to add items to the popup menu. PopUpAdd has three arguments: the popup menu object, the string to appear in the menu, and the function to call if that menu item is selected. Internally, PopUpAdd, adds an item to the menu and associates that item with this add-in and the function to call. 

Interpreting Menu Clicks

After a user clicks on a popup menu, every add-in is told which menu item the user selected. It is up to each add-in to determine if it should take some action.

The AutoDist add-in processes clicks with this code that

  1. Checks that the PopUpAdd function added the selected menu item

  2. Checks that this instance of this add-in added the selected menu item

  3. Uses the Visual Basic CallByName function to call a public function to process the click.

'===========================================
' This code could be useful to any add-in.
Private Sub m_DocIface_PopUpAfter(PopUp As Object, PopUpPlace As PopUpPlace, _
ItemIndex As Long, Handled As Boolean)
Dim aPopup As DTASMENULibCtl.DtMenu
Dim v As Variant

If Handled Then Exit Sub

Set aPopup = PopUp
v = aPopup.Reference(ItemIndex)

If Not IsArray(v) Then Exit Sub
If Not 0 = LBound(v) And 1 = UBound(v) Then Exit Sub
If Not IsObject(v(0)) Then Exit Sub
If Not v(0) Is Me Then Exit Sub

CallByName Me, v(1), VbMethod
Handled = True

End Sub
Public Sub DefaultDistSparse()
m_distenum = deCondSparse
End Sub
Public Sub DefaultDistCI()
m_distenum = deCondCI
End Sub

Other Issues

Forms

Although it is not demonstrated in this sample code, add-ins can have forms and they can show these forms in either modal or non-modal mode. Currently, forms that add-ins create are not children of the MSBNx MDI window.

Removing Items from a Menu

Although it is not demonstrated in this sample code, add-ins can hide items from the popup menu. For example, this code fragment hides the standard Node Add and Node Evaluate menu items. The constants to which this code refers, eDocCmdNodeAdd and eDocCmdNodeEvMenu, are members of  the MsbnXApp enumeration EDOCCMD.

    'Remove some items from the menu
Dim i As Long
For i = 1 To aPopup.Count
If aPopup.ItemVisible(i) Then
Select Case aPopup.ItemData(i)
Case eDocCmdNodeAdd, eDocCmdNodeEvMenu
aPopup.ItemVisible(i) = False
End Select
End If
Next i

Multiplicity of Documents, Models and Add-ins

A model can be associated with more than one document. A document can be associated with more than one add-in. A document can be associated with more than one instance of the same add-in.

Put another way, add-ins should be able share documents and models with other add-ins and with other instances of themselves.

Registration and Discovery

Explicit Registration

Add-ins should be registered under this key:

    HKEY_CURRENT_USER\Software\VB and VBA Program Settings\MsbnXApp\Global\Addins\

The value name is the add-in's ProgID, and value data is anything. For example:

    AutoDistLib.AutoDist="1"

Discovery

In addition to being registered by explicity configuration, users can navigate to an add-in library (DLL) and request that MSBNX load whatever add-ins are present. This is done through use of the primary string table in resource ensemble for the DLL.

Each string in a string table has a unique numeric identifier. The MSBNx add-in discovery method allocates values from 9900 higher. During browsing, MSBNX loads the DLL indicated by the user and examines the string table. It starts by requesting string 9900. If no such string exists, there are no discoverable add-ins in the library.

Each string number 9900 or higher should have as its string value the name of the automation library and its object exactly as it would be coded for a CreateObject call. For example,

9900 AutoDistLib.AutoDist

This string causes MSBNx to dynamically register the "prog ID" string "AutoDistLib.AutoDist" as available for use as an add-on.

MSBNx will then attempt to examine string 9901. The procedure stops when there are no more strings or there is a gap in the string numbers.

Application-Level Add-ins

MSBNx currently only supports document-level add-ins. Application-Level add-ins are not currently supported.