How to use the Enterprise Architect VBScript Library

The Enterprise Architect VBScript Library is an open source library of VBScripts written to be used in  Enterprise Architect.

This article explains how to download, import and use the library in Enterprise Architect.

Initial Import

Initial scripts

The Enterprise Architect VBScript Library contains some scripts to load and save scripts from/to your file system, but before we can use those we have to import an initial set of scripts to get started.

After downloading the file select menu option Project|Data Management|Import Reference Data and choose the downloaded file. Make sure to select Automation scripts and click Import.

This will import the minimum set of scripts required to load other scripts from the file system

 

 

[ecwid_product store_id=”6824265″ product_id=”58361080″]

Download library from GitHub

From the Enterprise Architect VBScript Library project page on GitHub you can either choose to download the library as a zip file, or download the repository in GitHub desktop.

VBScript library github

Load library into Enterprise Architect

Select scripts folderOne of the scripts in the initial set is the script LoadScripts in the group Script Management. If you execute this script you can choose the library folder from the library downloaded from GitHub.

The script will then scan the entire folder tree and it will load any .vbs file it can find.

For starters it might be interesting to only load the Frameworks scripts as they have the most chance of being useful to anyone.

If the LoadScripts script finds a script that already exists in Enterprise Architect it will ask to overwrite the existing scripts.

In order to know in which group the script belongs it will look for a the group indicator in the script.

'[group=Script Management]

This will tell the script that this script should go in the group Script Management. If the script indicator is absent it will assume the group name is the name of the folder where it was found.

After loading the scripts into EA make sure to press the refresh button to make the scripts appear in the GUI.

Refresh script tree

Saving all your scripts

In the script management group there also a script to save all your scripts to the file system.

In order to control where the script should go you can add the path indicator to the script like this

'[path=\Framework\Tools\Script Management]

The path indicator will control where to place the script relative to the chosen folder.

If the path indicator is absent the name of the script group will be used as name of the folder.

Being able to save and load the scripts from/to the file system now allows us to use version control on these scripts.

The library structure

The library is structured in two main parts.

  • Projects
    Contains an assortment of various scripts written for various projects.
  • Framework
    Contains the framework scripts that are meant to be used by other scripts

    • Utils
      Contains helper scripts and classes such as TextFile, SQL, XML etc..
    • Wrappers
      Contains the wrapper classes for EA elements such as TaggedValue, Script, ScriptGroup

The scripts in the projects folder can be used for inspiration, but it is mainly the scripts int he Framework part that are useful when writing scripts.

Using the library

The first thing you need to do when you want to use any of the framework classes is to include the framework in your script

!INC Wrappers.Include

This will include the “Include” script that takes care of including all the other scripts of the framework, including those of the Utils folder.

Then you can use the classes defined in the library. For example, if you want to have the user select a directory you can use following code

'get the folder from the user
dim folder
set folder = new FileSystemFolder
set folder = folder.getUserSelectedFolder("")

The classes in the library contain both properties as operations. You can use the properties as you would expect from “real” classes.

'show messagebox with the name of each subfolder
dim subfolders, subfolder
for each subfolder in folder.SubFolders
    msgbox "subfolder name: " & subfolder.Name
next

Contributing

Have you written any VBScripts for Enterprise Architect you would like to share? If you would like to contribute to the Enterprise Architect VBScript library you can email me at geert@bellekens.com

Tutorial: Generate complex documents from Enterprise Architect with a two-step semi-automated approach

  • Step 1: Create the virtual document with a script
  • Step 2: Generate the document from the virtual document

Document generation is important in most organisations that work with Enterprise Architect. Storing information in the model is good, but most stakeholders still require documents.

Documents are using as a tool for validation, as a work order, or just to give you an overview of a specific set of thing in the model. The main advantages of a document versus a model is that it has a limited scope and a linear order. You can read a document from start to finish, whereas you cannot do that with a model. Documents are, and will always be an important tool in developing applications.

Enterprise Architect comes with a sophisticated document generator that has seen great improvements in the last versions (v10, v11, v12). You also get a standard set of document templates that will quickly generate a document for a part of your model.

The problems arise however the moment you try to do something a bit more sophisticated with the document templates. Get information from different packages, use different templates for the same (set of) elements, etc… You will quickly realize that the approach of having one large template that generates the whole document is not useful for real-world document requirements.

Luckily EA has an alternative to the single template approach: Virtual Documents

Free Download

I’ve put together an example model and an MDG to demonstrate this approach. The MDG contains all scripts, templates and SQL Searches to generate a decent Use Case Document.

[ecwid_product store_id=”6824265″ product_id=”56803587″]

 

After you open the Hotellio.eap file, right click on the Reservations use case diagram and choose Create Use Case Document

Create Use Case Document

Then select the package to store the virtual document in and fill in the version.

Finally select the «master document» package and press F8 so start the document generation. Make sure to select the correct cover page and style sheet

Generate Document

Virtual Documents

Virtual Documents are used to define the contents of a document as a set of individual sections, each with its own source and template.

Virtual Documents structure

There are two types of elements that are used for virtual documents

  • Master Document
    Represents the document to be printed. You can give it some parameters that can be used in the document itself such as title, Version, Status, etc…
  • Model Document
    Represents a section in your document. The model document gets an RTFTemplate, and you have to define a source for this section. The source tell us which element(s) from the model should be used in this section. There are two ways to define the source. You can either drag one or more packages onto it, or you can fill in the Model Search and Search Term.

Master Documents get stored in the model as stereotyped packages and they contain stereotypes classes to represent the Model Documents

Virtual Document example

In the project browser this looks like thisVirtual Document project browser.

The Master Documents get the stereotype «master document» They contain tagged values to store things like ReportAlias, ReportTitle. Event though you can assign an RTF template to the master document this seems to be pointless since it gets ignored when printing a document.

Model Documents get the stereotype «model document». The links to the packages that serve as the source for a model document are created by dragging the package from the project browser onto the model document. This creates an attribute that maintains the link to the package.
Tagged values are used to indicate the template to use, the model search to use, and the search value to use on that search.

This mechanism of model documents allows you to very precisely control what will be printed in the document.

Templates

RTF templates are stored in the Resources section of your model. In order to see and manage them enable the resources view using View|Resources

template sections

Templates for document that have a package as source can start from the package tag and access all element, diagram etc.. in that package.

If your source is an SQL search that returns elements your template content can be placed in the element section

SQL Search

The SQL search I use to select a single element based on its GUID this following:

select c.ea_guid AS CLASSGUID,c.object_type AS CLASSTYPE,c.name AS Name, c.stereotype AS Stereotype,
package.name AS PackageName ,package_p1.name AS PackageLevel1,package_p2.name AS PackageLevel2,package_p3.name AS PackageLevel3 
from ((((t_object c 
inner join t_package  package on c.package_id = package.package_id) 
left join t_package  package_p1 on package_p1.package_id = package.parent_id) 
left join t_package  package_p2 on package_p2.package_id = package_p1.parent_id) 
left join t_package  package_p3 on package_p3.package_id = package_p2.parent_id) 
where 
c.ea_guid like '<Search Term>'

Printing

When you select the Master Document you can open up the document generation dialog by pressing F8

Generate Document

Here you can set the cover page, table of contents, and the style sheet to use.

Side note: Invest in the style sheet. It will save you time and frustration afterwards. It is difficult, and awkward to set styles using the RTF editor, but if you do it once in the stylesheet you don’t have to to it over and over again in each template. Also make sure to create your own “normal” style and call it “my normal” or something like that. Use this instead of the regular “normal”. There seems to be some weird behavior associated by the “normal” style, so I learned it’s best not to use it at all.

Then press generate, and the document generator will create one big document containing

  • The cover page
  • The table of contents page
  • All model documents in the order they appear in the project browser.

If you are not happy with the content of the document, or the order of the model documents then you can change them in EA and re-print the document.

One of the advantages is that the content of the document is stored in EA. So after a revision of the model you can print exactly the same content again and compare it with the previous version.

 Automatically generate virtual documents

Using virtual documents like that is all nice and good, but it still requires quite a lot of work to assemble the virtual document itself. For a somewhat realistic document you easily end up with tens of model documents, all pointing to different element or packages, and different template.
And you have to repeat that process for all the different documents you want to generate.

Doing the same thing over and over again is exactly what computers are build for. Instead of having to assemble the virtual document manually we will write a little script to do it for us.

The first thing well need to do is to make the Master Document package. This VBScript function does that for us.

function addMasterDocument(packageGUID, documentName,documentVersion,documentAlias)
	dim ownerPackage as EA.Package
	set ownerPackage = Repository.GetPackageByGuid(packageGUID)
	dim masterDocumentPackage as EA.Package
	set masterDocumentPackage = ownerPackage.Packages.AddNew(documentName, "package")
	masterDocumentPackage.Update
	masterDocumentPackage.Element.Stereotype = "master document"
	masterDocumentPackage.Alias = documentAlias
	masterDocumentPackage.Version = documentVersion
	masterDocumentPackage.Update
	'link to the master template
	dim templateTag as EA.TaggedValue
	for each templateTag in masterDocumentPackage.Element.TaggedValues
		if templateTag.Name = "RTFTemplate" then
			templateTag.Value = "(model document: master template)"
			templateTag.Notes = "Default: (model document: master template)"
			templateTag.Update
			exit for
		end if
	next
	'return
	set addMasterDocument= masterDocumentPackage
end function

Then we need to make the Model Documents.To make one with a Package as source we use this function

function addModelDocumentForPackage(masterDocument,package,name, treepos, template)
	dim modelDocElement as EA.Element
	set modelDocElement = masterDocument.Elements.AddNew(name, "Class")
	'set the position
	modelDocElement.TreePos = treepos
	modelDocElement.StereotypeEx = "model document"
	modelDocElement.Update
	'add tagged values
	dim templateTag as EA.TaggedValue
	for each templateTag in modelDocElement.TaggedValues
		if templateTag.Name = "RTFTemplate" then
			templateTag.Value = template
			templateTag.Notes = "Default: Model Report"
			templateTag.Update
			exit for
		end if
	next
	'add attribute
	dim attribute as EA.Attribute
	set attribute = modelDocElement.Attributes.AddNew(package.Name, "Package")
	attribute.ClassifierID = package.Element.ElementID
	attribute.Update
end function

To make one with an SQL Search as source we use this function

function addModelDocumentWithSearch(masterDocument, template,elementName, elementGUID, treepos, searchName)
	dim modelDocElement as EA.Element;
	set modelDocElement = masterDocument.Elements.AddNew(elementName, "Class")
	'set the position
	modelDocElement.TreePos = treepos
	modelDocElement.StereotypeEx = "model document"
	modelDocElement.Update
	dim templateTag as EA.TaggedValue
	if len(elementGUID) > 0 then
		for each templateTag in modelDocElement.TaggedValues
			if templateTag.Name = "RTFTemplate" then
				templateTag.Value = template
				templateTag.Notes = "Default: Model Report"
				templateTag.Update
			elseif templateTag.Name = "SearchName" then
				templateTag.Value = searchName
				templateTag.Update
			elseif templateTag.Name = "SearchValue" then
				templateTag.Value = elementGUID
				templateTag.Update
			end if
		next
	else
		'add tagged values
		for each templateTag in modelDocElement.TaggedValues
			if templateTag.Name = "RTFTemplate" then
				templateTag.Value = template
				templateTag.Notes = "Default: Model Report"
				templateTag.Update
				exit for
			end if
		next
		'no GUID provided. Set masterdocument package ID as dummy attribute to make the template work
		dim attribute as EA.Attribute
		set attribute = modelDocElement.Attributes.AddNew(masterDocument.Name, "Package")
		attribute.ClassifierID = masterDocument.Element.ElementID
		attribute.Update
	end if
end function

The the last script we is one to actually use these functions in order to create the virtual document. In the example provided I’ve split the script in two parts. One part that remains in the library, and one script that is put in the Diagram Group so we can call it from a diagram directly.

The script in the Diagram Group takes care of getting the user input

option explicit

!INC Bellekens DocGen.UseCaseDocument

' Script Name: Create Use Case Document
' Author: Geert Bellekens
' Purpose: Create the virtual document for a Use Case Document based on the open diagram
' 			Copy this script in a Diagram Group to call it from the diagram directly.
' Date: 11/11/2015
'

sub OnDiagramScript()
	dim documentsPackage as EA.Package
	'select the package to generate the virtual document in
	Msgbox "Please select the package to generate the virtual document in",vbOKOnly+vbQuestion,"Document Package"
	set documentsPackage = selectPackage()
	if not documentsPackage is nothing then
		' Get a reference to the current diagram
		dim currentDiagram as EA.Diagram
		set currentDiagram = Repository.GetCurrentDiagram()
		if not currentDiagram is nothing then
			createUseCaseDocument currentDiagram, documentsPackage.PackageGUID 
			Msgbox "Select the Master Document and press F8 to generate document",vbOKOnly+vbInformation,"Finished!"
		else
			Session.Prompt "This script requires a diagram to be visible", promptOK
		end if
	end if
end sub

OnDiagramScript

The script in the library will use the functions mentioned above to start building the virtual document


!INC Local Scripts.EAConstants-VBScript
!INC Bellekens DocGen.DocGenHelpers
!INC Bellekens DocGen.Util
'

' Script Name: UseCaseDocuemnt
' Author: Geert Bellekens
' Purpose: Create the virtual document for a Use Case Document based on the given diagram
' Date: 11/11/2015
'

dim useCaseDocumentsPackageGUID

function createUseCaseDocument( diagram, documentsPackageGUID)
	
	useCaseDocumentsPackageGUID = documentsPackageGUID
	'first create a master document
	dim masterDocument as EA.Package
	set masterDocument = makeUseCaseMasterDocument(diagram)
	if not masterDocument is nothing then
		dim i
		i = 0
		'use case diagram part 1
		addModelDocumentForDiagram masterDocument,diagram, i, "UCD_Use Case Diagram"
		i = i + 1
		'add Actors
		dim diagramPackage as EA.Package
		set diagramPackage = Repository.GetPackageByID(diagram.PackageID)
		addModelDocumentForPackage masterDocument, diagramPackage, diagram.Name & " Actors", i, "UCD_Actors"
		i = i + 1
		' We only want to report the use cases that are shown within the scope boundary on this diagram
		'get the boundary diagram object in the diagram
		dim boundaries
		set boundaries = getDiagramObjects(diagram,"Boundary")
		Session.Output boundaries.Count
		'get the use cases
		dim usecases		
		if boundaries.Count > 0 then
			set usecases = getElementsFromDiagramInBoundary(diagram, "UseCase",boundaries(0))
			Session.Output "boundary found"
		else
			set usecases = getElementsFromDiagram(diagram, "UseCase")
		end if
		
		'sort use cases alphabetically
		set usecases = sortElementsByName(usecases)
		
		'add the use cases
		i = addUseCases(masterDocument, usecases, i)
		
		Repository.RefreshModelView(masterDocument.PackageID)
		'select the created master document in the project browser
		Repository.ShowInProjectView(masterDocument)
	end if
end function


function makeUseCaseMasterDocument(currentDiagram)
	'we should ask the user for a version
	dim documentTitle
	dim documentVersion
	dim documentName
	dim diagramName
	set makeUseCaseMasterDocument = nothing
	diagramName = currentDiagram.Name
	'to make sure document version is filled in
	documentVersion = ""
	documentVersion = InputBox("Please enter the version of this document", "Document version", "x.y.z" )
	if documentVersion <> "" then
		'OK, we have a version, continue
		documentName = "UCD - " & diagramName & " v. " & documentVersion
		dim masterDocument as EA.Package
		set masterDocument = addMasterDocumentWithDetails(useCaseDocumentsPackageGUID, documentName,documentVersion,diagramName)
		set makeUseCaseMasterDocument = masterDocument
	end if
end function

'add the use cases to the document
function addUseCases(masterDocument, usecases, i)
	dim usecase as EA.Element
	for each usecase in usecases
		'use case part 1
		addModelDocument masterDocument, "UCD_Use Case details part1", "UC " & usecase.Name & " Part 1", usecase.ElementGUID, i
		i = i + 1
		
		'get the nested scenario diagram
		dim activity as EA.Element
		set activity = getActivityForUsecase(usecase)
		
		'add scenario diagram
		if not activity is nothing then
		addModelDocument masterDocument, "UCD_Use Case Scenarios Diagram", "UC " & usecase.Name & " Scenarios diagram", activity.ElementGUID, i
			i = i + 1
		end if
		
		'use case part 2
		addModelDocument masterDocument, "UCD_Use Case details part2","UC " &  usecase.Name & " Part 2", usecase.ElementGUID, i
		i = i + 1
		
	next
	'return the new i
	addUseCases = i
end function

function getActivityForUsecase(usecase)
	set getActivityForUsecase = getNestedDiagramOnwerForElement(usecase, "Activity")
end function

function getInteractionForUseCase(usecase)
	set getInteractionForUseCase = getNestedDiagramOnwerForElement(usecase, "Interaction")
end function

function getNestedDiagramOnwerForElement(element, elementType)
	dim diagramOnwer as EA.Element
	set diagramOnwer = nothing
	dim nestedElement as EA.Element
	for each nestedElement in element.Elements
		if nestedElement.Type = elementType and nestedElement.Diagrams.Count > 0 then
			set diagramOnwer = nestedElement
			exit for
		end if
	next
	set getNestedDiagramOnwerForElement = diagramOnwer
end function


'sort the elements in the given ArrayList of EA.Elements by their name 
function sortElementsByName (elements)
	dim i
	dim goAgain
	goAgain = false
	dim thisElement as EA.Element
	dim nextElement as EA.Element
	for i = 0 to elements.Count -2 step 1
		set thisElement = elements(i)
		set nextElement = elements(i +1)
		if  elementIsAfter(thisElement, nextElement) then
			elements.RemoveAt(i +1)
			elements.Insert i, nextElement
			goAgain = true
		end if
	next
	'if we had to swap an element then we go over the list again
	if goAgain then
		set elements = sortElementsByName (elements)
	end if
	'return the sorted list
	set sortElementsByName = elements
end function

'check if the name of the next element is bigger then the name of the first element
function elementIsAfter (thisElement, nextElement)
	dim compareResult
	compareResult = StrComp(thisElement.Name, nextElement.Name,1)
	if compareResult > 0 then
		elementIsAfter = True
	else
		elementIsAfter = False
	end if
end function

How to use the Enterprise Architect Add-in Framework

Since I started writing the EA Navigator and other tools the code has been open source and available on Github. The code is split up in a tool-specific part for the EA Navigator and some other tools, and a framework that can be used to build your own tool.

 Downloading

The code for the framework and the tools is published on GitHub, a version control repository “in the cloud”, which is free for public repositories. The easiest way to download the code is to install GitHub for Windows. That allows you use the Clone in Desktop button on Github.

Clone in Desktop

You can also download the ZIP, but then you’ll have to rename the packages to their original name. To be able to use the framework you’ll need two repositories

If you want to see how the EA Navigator uses the framework you’ll need

Make sure you put all the projects in the same directory as otherwise the references would be broken.

 Compiling

The code for the EA Add-in framework is written in C# using SharpDevelop. You can also open it with Visual Studio or another C# IDE, but then you’ll have to remove the wix projects from the solution.

You can open the complete code in either the Enterprise-Architect-Toolpack \Total Solution.sln or in case you only downloaded the framework then Enterprise-Architect-Add-in-Framework\Total Solution.sln but make sure to start you IDE as administrator.

Before compiling check the reference to Interop.EA. I’ve set it to point to C:\Program Files (x86)\Sparx Systems\EA\Interop.EA.dll but if you have EA installed in another directory you’ll need to replace the existing reference.

Once the references are OK you should be able to compile the solution without any issues.

 The pattern

UML Tooling Framework
To be able to understand the architecture of the framework you’ll have to understand the pattern behind it.

On the right you see a diagram of the general modelling tooling pattern. This pattern describes how to create tools that extend the features of a standard modelling tool in order to support the modelling method used.

This is the pattern I use whenever I develop an Enterprise Architect add-in for a client.

The LanguageObject is an interface that represent the object of the modelling language. In the case of UML each meta class of the meta model is represented as an interface. The LanguageObjects are completely independent of the modelling method or modelling tool used. Once finished this layer hardly ever changes.

The LanguageObject is then implemented by the ToolWrapper. There will be a ToolWrapper layer for each supported case tool. The ToolWrapper wraps the ToolObjects of the case tools API using the adapter pattern.

The MetaObject is only required when you are implementing a tool for a specific modelling method. These MetaObjects represent the meta classes of the modelling methods meta model. They wrap the LanguageObject using the adapter pattern. General tools that are not specific to any modeling method, such as the EA Navigator can skip this layer and use the LanguageObjects directly.

The Client is the actual tool. It only uses the MetaObjects to actually do its job.

In order for this pattern to actually work, it is really important that the dependencies and layers are respected.

 The architecture

To make an add-in you’ll always need three components similar to the three repositories on GitHub. You can see how these component map onto the pattern on the diagram.

Add-in Architecture

UML Tooling Framework

The UML Tooling framework implements the LanguageObjects of the pattern for the UML language. Each element of the UML meta model is represented as a interface defining all attributes and operations as defined in the UML Superstructure.UML Tooling Framework Code

Enterprise Architect Addin Framework

This component implements the ToolWrappers of the pattern. This component actually knows about the Enterprise Architect API. It hides all the ugliness of the API behind a clean set of UML interface implementations. There is a also a bunch of helper functions and stuff like that to make things a bit easier to work with.

EA Addin Framework Code

The Enterprise Architect Add-in Framework also contains an abstract add-in class EAAddinBase that you can inherit from to quickly create a new add-in.

The add-in component

The add-in component is the part that is specific to the add-in. It contains the actual logic and behavior that needs to be executed. This is also that part that you tell Enterprise Architect about. EA will then call the prescribed methods such as EA_GetMenuItem or EA_MenuClick on the AddinClass  at the appropriate moments.

When done correctly only the EA_ operations use API objects in their parameters. The first thing they should do is create the appropriate wrapper class and execute the required behavior on that wrapper. We cannot avoid the dependency to EA’s API because of the parameters in the EA_ methods, but it should be restricted to the parameters only. Make sure the only class that depends on Interop.EA.dll is the AddinClass, and leave this class as bear bones as possible. All functionality should go into functional classes who don’t depend on Interop.EA.dll.

You could even consider to split the MyAddin.dll into two parts, one for only the AddinClass, and one for the functional classes. This would definitely be the preferred architecture once you have multiple add-ins that somehow can re-use some of the functional classes.

 More resources

 Related articles

Source code on GitHub

Sparx Systems

Tutorial: How to change the color of inactive selected items in Windows 7

Since I upgraded to a Windows 7 machine I had a problem seeing which element was selected in my Enterprise Architect project browser.

Not when it was selected and active (dark-blue), but when I went on to work on something in a diagram.

The standard “inactive selection color” of Windows 7 is just too faint.

To illustrate that, look at the picture on the left and try to figure out which element is actually selected.

Well, if you look really carefully you’ll notice that the background of “Primitive1” is actually just the slightest bit darker then the rest of the background.

When I first noticed this I tried to fix it by changing my monitor settings, but no amount of brightness, contrast  or color balance changes made the selection more visible without messing up everything else.

So then I started googling and I soon found the solution:

  • Select Start|Control Panel|Change the Theme
  • Once there choose Window Color
  • And Advanced appearance settings…
  • Then choose 3D objects as item and click on the dropdown for Color 1:  and click on Other…
  • Here you move the triangle on the right just a few nudges down
  • Now OK and Apply and wait until Windows has applied the settings.
  • Now check the inactive selection color again in EA. If all is well it should now look like this:
  • To be sure that you don’t loose this setting you can save your theme in the personalization window.

Tutorial: Deploy your Enterprise Architect C# add-in with an MSI package

In previous posts I talked about Creating and Testing your Enterprise Architect C# add-in, and how to use the C# add-in template to speed up the development process.

Once created and tested you’ll probably want to install and use the add-in on other computers then your development machine. This tutorial explains how to create an MSI installer package using the open source SharpDevelop and WiX software.

The reason I’m using SharpDevelop as opposed to Visual Studio C# Express is because SharpDevelop is free and open source, and Visual Studio C# Express doesn’t allow to create setup projects, or attach to a running process to debug your add-in.

Requirements

The plan

As explained in Tutorial: Create your first C# Enterprise Architect addin in 10 minutes we need our installer to do three things for us:

  1. Copy the dll’s to a program folder.
  2. Register the add-in dll for COM Interop so Windows knows where to find the add-in dll.
  3. Create the registry key so EA knows about our add-in.

Step 1: Create a setup project

  • Open the solution for your add-in in SharpDevelop. If you have developed the add-in with Visual Studio, that fine. Sharpdevelop uses the exact same project setup as Visual Studio, with the .sln and .csproj files
  • Add a new project and choose for Setup Project – WixUI Minimal
    You can of course choose for another template, but for the purpose of this tutorial the WixUI Minimal is enough.
    New WiX Setup Project
  • SharpDevelop will now have created a new Setup project in your solution containing three files:
    • Files.wxs
      This is were we define wich files and registry keys the installer needs to install
    • license.rtf
      Contains the license for your software
    • Setup.wxs
      Where we define the general setup of the installer like the name, and the components to install.
    • MyAddinProject

Step 2: Edit Setup.wxs

Next we’re going to tell the installer about the application it will install. The .wxs files are actually XML files, and you can edit them directly in SharpDevelop.

  • Open Setup.wxs and edit the highlighted parts
    Setup.wxs
    Notice that we also added a ComponentRef for MyAddinRegEntries.

Step 3: Add the program files

  • Open up the setup files view with View|Setup|Files
  • Rename the installation folder to something sensible
  • Add a component for each ComponentRef we added in the Setup.wxs
  • Right-click on the MyAddinFiles component and choose Add Files…
    MyAddinAddFiles
  •  Now browse to the MyAddin project folder and select all files in the MyAddin\bin\debug or MyAddin\bin\release folder, depending on how you last built your project.
    Not all of these files might be necessary, but I can’t be bothered to figure out exactly which files we need to install the add-in on another computer.
  • This should now have created a line in the files.wxs for each binary file similar to
    <File Id="EAAddinFramework.dll" Name="EAAddinFramework.dll" Source="..\MyAddin\bin\Debug\EAAddinFramework.dll" />
    <File Id="UMLToolingFramework.dll" Name="UMLToolingFramework.dll" Source="..\MyAddin\bin\Debug\UMLToolingFramework.dll" />
    <File Id="EAAddinFramework.pdb" Name="EAAddinFramework.pdb" Source="..\MyAddin\bin\Debug\EAAddinFramework.pdb" />
    <File Id="Interop.EA.dll" Name="Interop.EA.dll" Source="..\MyAddin\bin\Debug\Interop.EA.dll" />
    <File Id="MyAddin.pdb" Name="MyAddin.pdb" Source="..\MyAddin\bin\Debug\MyAddin.pdb" />
    <File Id="MyAddin.tlb" Name="MyAddin.tlb" Source="..\MyAddin\bin\Debug\MyAddin.tlb" />
    <File Id="UMLToolingFramework.pdb" Name="UMLToolingFramework.pdb" Source="..\MyAddin\bin\Debug\UMLToolingFramework.pdb" />
    <File Id="MyAddin.dll" Name="MyAddin.dll" Source="..\MyAddin\bin\Debug\MyAddin.dll" />
    

Step 4: Register MyAddin.dll for COM Interop

The MyAddin.dll is the add-in dll that EA needs to talk to, so this dll needs to be registered for COM Interop. This is the part that is done by regasm.exe when doing a manual installation, but we don’t want to be calling any executables on the target machine during install if we can avoid it. Instead we will just add the same registry keys that regasm.exe creates.

  • Open a command prompt in the folder where MyAddin.dll is located (bin\Debug\)
  • Execute following command:
    "C:\Program Files (x86)\WiX Toolset v3.8\bin\heat.exe" file MyAddin.dll -ag -template fragment -out MyAddin.wxs

    This will have created a file in the same folder with the name MyAddin.wxs.

  • Open that file and copy the contents of the Component tag:
    <Class Id="{10BC65F1-32C0-3ED4-98A0-17661A8C4455}" Context="InprocServer32" Description="MyAddin.MyAddinClass" ThreadingModel="both" ForeignServer="mscoree.dll">
     <ProgId Id="MyAddin.MyAddinClass" Description="MyAddin.MyAddinClass" />
    </Class>
    <File Id="filCC4172BEC1312562EDEF49648E45AE0D" KeyPath="yes" Source="..\MyAddin\bin\Debug\MyAddin.dll" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Value="" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="Class" Value="MyAddin.MyAddinClass" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="Assembly" Value="MyAddin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="RuntimeVersion" Value="v4.0.30319" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="CodeBase" Value="file:///[#filCC4172BEC1312562EDEF49648E45AE0D]" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="Class" Value="MyAddin.MyAddinClass" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="Assembly" Value="MyAddin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="RuntimeVersion" Value="v4.0.30319" Type="string" Action="write" />
    <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="CodeBase" Value="file:///[#filCC4172BEC1312562EDEF49648E45AE0D]" Type="string" Action="write" />
  • Replace the file tag for MyAddin.dll in your files.wxs by the copied part
  • Add a Name attribute Name=”MyAddin.dll to the file tag for MyAddin.dll so the tag looks like:
    <File Id="filCC4172BEC1312562EDEF49648E45AE0D" Name="MyAddin.dll" KeyPath="yes" Source="..\MyAddin\bin\Debug\MyAddin.dll" /> 

Step 5: Add the EA Addin registry key

This step will instruct the installer to create the add-in registry key that tells EA there is an add-in to load

  • Add following Registry key tag to the MyAddinRegEntries tag in the Files.wxs
    <RegistryKey Root="HKCU" Key="Software\Sparx Systems\EAAddins\MyAddin" Action="createAndRemoveOnUninstall">
    <RegistryValue Type="string" Value="MyAddin.MyAddinClass" />
     </RegistryKey>
    

All xml file editing should now be behind us. The complete Files.wxs should look something like:

<?xml version="1.0"?>
   <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
     <Fragment>
       <DirectoryRef Id="TARGETDIR">
         <Directory Id="ProgramFilesFolder" Name="PFiles">
           <Directory Id="INSTALLDIR" Name="MyAddin">
             <Component Id="MyAddinFiles" Guid="5C526B21-FB62-42AD-9897-F990FEA7E164" DiskId="1">
               <File Id="EAAddinFramework.dll" Name="EAAddinFramework.dll" Source="..\MyAddin\bin\Debug\EAAddinFramework.dll" />
               <File Id="UMLToolingFramework.dll" Name="UMLToolingFramework.dll" Source="..\MyAddin\bin\Debug\UMLToolingFramework.dll" />
               <File Id="EAAddinFramework.pdb" Name="EAAddinFramework.pdb" Source="..\MyAddin\bin\Debug\EAAddinFramework.pdb" />
               <File Id="Interop.EA.dll" Name="Interop.EA.dll" Source="..\MyAddin\bin\Debug\Interop.EA.dll" />
               <File Id="MyAddin.pdb" Name="MyAddin.pdb" Source="..\MyAddin\bin\Debug\MyAddin.pdb" />
               <File Id="MyAddin.tlb" Name="MyAddin.tlb" Source="..\MyAddin\bin\Debug\MyAddin.tlb" />
               <File Id="UMLToolingFramework.pdb" Name="UMLToolingFramework.pdb" Source="..\MyAddin\bin\Debug\UMLToolingFramework.pdb" />
               <Class Id="{10BC65F1-32C0-3ED4-98A0-17661A8C4455}" Context="InprocServer32" Description="MyAddin.MyAddinClass" ThreadingModel="both" ForeignServer="mscoree.dll">
                 <ProgId Id="MyAddin.MyAddinClass" Description="MyAddin.MyAddinClass" />
               </Class>
               <File Id="filCC4172BEC1312562EDEF49648E45AE0D" Name="MyAddin.dll" KeyPath="yes" Source="..\MyAddin\bin\Debug\MyAddin.dll" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Value="" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="Class" Value="MyAddin.MyAddinClass" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="Assembly" Value="MyAddin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="RuntimeVersion" Value="v4.0.30319" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32\1.0.0.0" Name="CodeBase" Value="file:///[#filCC4172BEC1312562EDEF49648E45AE0D]" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="Class" Value="MyAddin.MyAddinClass" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="Assembly" Value="MyAddin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="RuntimeVersion" Value="v4.0.30319" Type="string" Action="write" />
               <RegistryValue Root="HKCR" Key="CLSID\{10BC65F1-32C0-3ED4-98A0-17661A8C4455}\InprocServer32" Name="CodeBase" Value="file:///[#filCC4172BEC1312562EDEF49648E45AE0D]" Type="string" Action="write" />
             </Component>
             <Component Id="MyAddinRegEntries" Guid="8439A7F4-D42F-43AC-B91D-CBF823A89DDE">
              <RegistryKey Root="HKCU" Key="Software\Sparx Systems\EAAddins\MyAddin" Action="createAndRemoveOnUninstall">
                <RegistryValue Type="string" Value="MyAddin.MyAddinClass" />
              </RegistryKey>
            </Component>
          </Directory>
        </Directory>
      </DirectoryRef>
    </Fragment>
 </Wix>

Step 6: Fill in the license details

  • Open license.rft in wordpad, or another rtf editor and fill in the license details
    Do NOT use Microsoft Word to edit the license.rtf as Word will make a horrible mess of the rtf file.

Step 7: Build and test your setup project

Now all we need to do is build and test the setup project. You can run it right from within Sharpdevelop.

If all is well you should see something similar to this:

More resources

Related blog posts

Source code on GitHub

Sparx Systems

Other

  • Examples in the EA installation folder: C:\Program Files\Sparx Systems\EA\Code Samples

Tutorial: Create your first C# Enterprise Architect addin in 10 minutes

Enterprise Architect from Sparx Systems is a great UML Case tool, but you can make it even better by adding your own functionality in the form of an add-in.

This post will take you through the basic steps to create your first C# EA add-in  in about 10 minutes.

You can use a number of programming languages to create add-ins for EA, but personally I like C# the best.

Prerequisites

Before you start you should have following software on your computer ready to use:

  • Enterprise Architect (download the fully functional trial if you don’t have it installed yet)
  • Visual Studio (I’m using the free Visual C# 2010 Express for this tutorial)

EA’s addin architecture

To fully understand the steps necessary to get your add-in running you should first understand how EA’s add-in architecture works.

When EA starts up it will read the registry key [HKEY_CURRENT_USER\Software\Sparx Systems\EAAddins]. Each of the keys in this location represents an add-in for EA to load.

The (default) value of the key contains the name of the assembly and the name of the add-in class separated with a dot.

EA then asks Windows  for the location of the assembly, which is stored on the COM codebase entries in the registry, and it will use the public operations defined in the add-in class.

So in order for our add-in to work we’ll need to:

  • Create the add-in dll containing the add-in class
  • Add a key to registry containing the name of the assembly and the name of the add-in class
  • Register the in the COM codebase entries in the registry

Step 1: Create the add-in dll

So open up Visual Studio, start a new project, and choose Class Library as type of project.

The first thing we need to do is to add the EA API assembly to our references, so choose Add Reference….

Select the Browse tab, browse tot the EA installation folder (default: C:\Program Files\Sparx Systems\EA) and choose the file Interop.EA.dll.
This will allow us to use the classes/interfaces defined by EA’s API.

And since we will be using a messagebox for our first addin, select the .NET tab, scroll down and select System.Windows.Forms

Then there are some build options we need to configure.
First we’re going to tell Visual Studio to build our dll so it can be used as a COM object.
Doubleclick on on the properties folder under your project, click on the button Assembly Information and tick the little checkbox on the bottom that says Make assembly COM-visible

Then we would also like Visual Studio to register the dll in the COM codebase entries in the registry each time it builds our little project.

To do so go into the Build tab of the project properties and tick the checkbox Register for COM interop.

Then we rename the default Class1.cs to MyAddinClass.cs and replace the existing template code by the following:

using System;
using System.Windows.Forms;

namespace MyAddin
{
    public class MyAddinClass
    {
        // define menu constants
        const string menuHeader = "-&MyAddin";
        const string menuHello = "&Say Hello";
        const string menuGoodbye = "&Say Goodbye";

        // remember if we have to say hello or goodbye
        private bool shouldWeSayHello = true;

        ///
        /// Called Before EA starts to check Add-In Exists
        /// Nothing is done here.
        /// This operation needs to exists for the addin to work
        ///
        /// <param name="Repository" />the EA repository
        /// a string
        public String EA_Connect(EA.Repository Repository)
        {
            //No special processing required.
            return "a string";
        }

        ///
        /// Called when user Clicks Add-Ins Menu item from within EA.
        /// Populates the Menu with our desired selections.
        /// Location can be "TreeView" "MainMenu" or "Diagram".
        ///
        /// <param name="Repository" />the repository
        /// <param name="Location" />the location of the menu
        /// <param name="MenuName" />the name of the menu
        ///
        public object EA_GetMenuItems(EA.Repository Repository, string Location, string MenuName)
        {

                switch (MenuName)
                {
                    // defines the top level menu option
                    case "":
                        return menuHeader;
                    // defines the submenu options
                    case menuHeader:
                        string[] subMenus = { menuHello, menuGoodbye};
                        return subMenus;
                }

            return "";
        }

        ///
        /// returns true if a project is currently opened
        ///
        /// <param name="Repository" />the repository
        /// true if a project is opened in EA
        bool IsProjectOpen(EA.Repository Repository)
        {
            try
            {
                EA.Collection c = Repository.Models;
                return true;
            }
            catch
            {
                return false;
            }
        }

        ///
        /// Called once Menu has been opened to see what menu items should active.
        ///
        /// <param name="Repository" />the repository
        /// <param name="Location" />the location of the menu
        /// <param name="MenuName" />the name of the menu
        /// <param name="ItemName" />the name of the menu item
        /// <param name="IsEnabled" />boolean indicating whethe the menu item is enabled
        /// <param name="IsChecked" />boolean indicating whether the menu is checked
        public void EA_GetMenuState(EA.Repository Repository, string Location, string MenuName, string ItemName, ref bool IsEnabled, ref bool IsChecked)
        {
            if (IsProjectOpen(Repository))
            {
                switch (ItemName)
                {
                    // define the state of the hello menu option
                    case menuHello:
                        IsEnabled = shouldWeSayHello;
                        break;
                    // define the state of the goodbye menu option
                    case menuGoodbye:
                        IsEnabled = !shouldWeSayHello;
                        break;
                    // there shouldn't be any other, but just in case disable it.
                    default:
                        IsEnabled = false;
                        break;
                }
            }
            else
            {
                // If no open project, disable all menu options
                IsEnabled = false;
            }
        }

        ///
        /// Called when user makes a selection in the menu.
        /// This is your main exit point to the rest of your Add-in
        ///
        /// <param name="Repository" />the repository
        /// <param name="Location" />the location of the menu
        /// <param name="MenuName" />the name of the menu
        /// <param name="ItemName" />the name of the selected menu item
        public void EA_MenuClick(EA.Repository Repository, string Location, string MenuName, string ItemName)
        {
            switch (ItemName)
            {
                // user has clicked the menuHello menu option
                case menuHello:
                    this.sayHello();
                    break;
                // user has clicked the menuGoodbye menu option
                case menuGoodbye:
                    this.sayGoodbye();
                    break;
            }
        }

        ///
        /// Say Hello to the world
        ///
        private void sayHello()
        {
            MessageBox.Show("Hello World");
            this.shouldWeSayHello = false;
        }

        ///
        /// Say Goodbye to the world
        ///
        private void sayGoodbye()
        {
            MessageBox.Show("Goodbye World");
            this.shouldWeSayHello = true;
        }

        ///
        /// EA calls this operation when it exists. Can be used to do some cleanup work.
        ///
        public void EA_Disconnect()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

    }
}

Now all we need to do is build the project, and the add-in dll is finished.

Step 2: Add the registry key

In order to let EA know there is a new add-in to be loaded we need to add a key in the registry in the location: HKEY_CURRENT_USER\Software\Sparx Systems\EAAddins

To do so open up the Windows registry editor by clicking Start|Run and type regedit

Then browse to the HKEY_CURRENT_USER\Software\Sparx Systems\EAAddins key, right click and add a key with the name of the project in Visual Studio, in our case MyAddin

The registry editor will automatically create a default value for the new key. Doubleclick on the (Default) value on the right pane, and enter the value in the form of [ProjectName].[ClassName], so in this case MyAddin.MyAddinClass

Step 3: Try it out in EA

Alright, now the add-in is ready to be used. So fire up EA, open a project and right click on an element in the projectbrowser, or a diagram.

You should now see an additional menu option with the options we defined.

Congratulations! You have just created your first C# add-in for EA

Distributing the add-in

Once you have finished your add-in you will probably want to share it with others.

There are three steps needed to install your add-in on another computer:

  1. Copy the required files to a convenient location.
    You can find the files needed in your Visual Studio project folder: ..\MyAddin\MyAddin\bin\Release
  2. Register your add-in dll in the COM codebase entries in the registry using regasm.exe
    Open up a command prompt in folder where you copied the add-in dll and register the dll with the /codebase option. In my case that command would be: %WINDIR%\Microsoft.NET\Framework\v4.0.30319\regasm MyAddin.dll /codebase
  3. Add the registry key
    The easiest way to add the registry key on another computer is to export the key from your registry using regedit. This will save the information stored in the key in a .reg file, which you can execute by doubleclicking.

These three steps can of course be automated by your favorite installer program.

More resources

Related articles

Source code on Github

Sparx Systems

Other