You are on page 1of 54

Learning MicroStation Addins Step by Step[0]

A series of posts in this blog will help you to learn the MicroStation Addins step by step, from
elementary topics to advanced. According to the steps provided in these blogs, I believe you can
quickly master this new approach of developing MicroStation application. I wrote these blogs in
bilingual edition, Chinese and English, for the convenience of developers in China and other
territories. For Chinese, you can read the text in blue; For English, you can read the text in black.
Limited to my poor English level, I can't ensure the correctness of English edition. Just for your
information.
Chapter 0. Introduction and Prerequisite
As of V8XM(08.09.xx.xx) a new development approach is introduced into MicroStation, we called
this approach Addins. Addins is based on .NET Framework, thus you can use C#, C++/CLI or
VB.NET language to develop Addins application. Compared to MVBA(MicroStation Visual Basic for
Application), Addins can support command table and can be compiled into DLL. Compared to
MDL(MicroStation Development Language/Library), you can use WinForm to design user interface
in Addins and you can never to touch the .r resource which is tough for novice.
To learn Addins programming, You'd better know and be able to use Bentley MicroStation(shorten as
Mstn from now on) and Microsoft Visual Studio(shorten as VS from now on), plus have a basic
knowledge of C# computer language. These three parts are not included in this series of posts, you
have to find them by yourself. But don't be worry, there are a lot of Mstn related contents in Be
Communities (you are accessing it) and there are plenty of VS and C# related contents in Internet.
Say less, do more. Now let's start to act. Please install VS2010 and MicroStation V8i(SELECT series
2). After you successfully install these two softwares and try to start them, you will see their start
pages shown as below. My VS2010 is installed at C:\VisualStudio\2010 and my MstnV8iSS2 is
installed at F:\Bentley\0811\MicroStation. I would like to complement a note, you can develop
Addins under Mstn V8iSS2 using VS2005, VS2008 or VS2010. Express, Professional or Enterprise
versions are all ok.
Learning MicroStation Addins Step by Step[1]
Chapter 1. Creating a simplest Addin application
This chapter will lead you to create a simplest 'Hello World' Addin application from scratch. This
application can be loaded and executed in Mstn.
1. Start VS2010.
2. The below form will display by selecting the menu File > New > Project or directly pressing the
shortcut keys Ctrl+Shift+N in VS2010. In this form, find and click Visual C# in the left pane and
clickClass Library in the right pane. Then, select a project location and input a name of project.
Before you click the OK button, remember to change the .NET Framework from default 4.0 to 3.5 !
This is very important because Mstn V8iSS2 can only support up to .NET Framework 3.5. (Actually
.NET 3.5 is based on .NET 2.0. We can verify this if you are interested in it.)

Now, click the OK button will bring the below interface. The C# code is generated at left pane and
theSolution Explorer is in right pane.

3. Right-click the name of project csAddins in Solution Explorer and select the Properties in the popup menu to open the Project Properties form. Under the Build page, click the Browse... button after
theOutput path textbox to select mdlapps folder under your Mstn. For my case, this path is finally
set to F:\Bentley\0811\MicroStation\mdlapps\. (If your Addins DLL is generated in your specified
folder not in this mdlapps folder, you should set configuration variable MS_ADDINPATH to your
folder in order to load your Addin correctly from Mstn.)

4. Modify the reference in this project. In this step we will add four references: one is .NET library,
three are Mstn managed libraries. We can also delete four unnecessary references generated by
wizard.
4.1. Right-click the References item in Solution Explorer and then select the Add Reference... item
in the pop-up menu. In the Add Reference form, select .NET page, find and select
theSystem.Windows.Forms line and then click OK button to add this reference. The class
MessageBox we will use in our code needs this library.

4.2. Select Browse page in the above form, browse and select the following three dynamic libraries to
add them.
\MicroStation\ustation.dll
\MicroStation\assemblies\bentley.microstation.interfaces.1.0.dll
\MicroStation\assemblies\ECFramework\bentley.general.1.0.dll

4.3. Delete four unnecessary references which we don't need them now. Final reference list is shown
as below:

5. This step is optional, but you 'd better do this. Right-click Class1.cs in Solution Explorer and then
select Rename from the pop-up menu. Change the Class1.cs to MyAddin.cs. When you face the
below prompt, please click the Yes button to continue.

6. Modify MyAddin.cs source code to the below snippet:


using System;
using System.Windows.Forms;

namespace csAddins
{
internal sealed class MyAddin : Bentley.MicroStation.AddIn
{
private MyAddin(System.IntPtr mdlDesc) : base(mdlDesc)
{
}
protected override int Run(string[] commandLine)
{
MessageBox.Show("Hello World");
return 0;
}
}
}
If a .NET assembly can be executed as an Addin under Mstn, it must meet three conditions: a)
Includes one class derived from Bentley.MicroStation.Addin; b) This derived class must supply a
single argument constructor that take an MDL descriptor as an int; That constructor must chain to the
base class constructor and pass the argument along; c) This derived class must override the virtual
Run() method of the base Addin class. The above definition of MyAddin just meets these three
conditions.
7. Build this solution by selecting the Build > Build Solution menu in VS. When you see the below
message in output window of VS, it denotes that you have created an Mstn Addin successfully. If you
unluckily see 0 succeeded, x failed message, you have to check the above steps carefully. Checking
the errors description can help you to locate the place of error.
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
8. Now lets verify our Addin program. Please start Mstn. Type MDL LOAD csAddins in the key-in
field and then press Enter. You will see a "Hello World" message box displayed. By far, we have
introduced to you all the steps about how to create a simplest Addin. Note: You can open the Key-in
dialog box by selecting Mstns menu Utilities > Key-in.
9. If you want, you can continue to do a more step. In this step, we will verify that .NET 3.5 is based
on .NET 2.0 (.NET 4.0 is not based on .NET 2.0). First, exit Mstn to unload csAddins application
because we cant unload an Addin using MDL UNLOAD command. We will discuss the approach
about how to unload an Addin without exiting Mstn in late chapter; Second, change the code of Run()
method of MyAddins.cs file as the below snippet. Then, build the solution in VS (refer to step 7);
Finally, restart Mstn and load csAddins(refer to step 8). You will see the message box shown as the
following, in which, the installed path of .NET Framework is shown. We can easily find .NET 3.5 is
based on .NET 2.0 from the v2.0.50727 character string.

string sWinFrameworkPath =
System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
MessageBox.Show("Framework Path =" + sWinFrameworkPath);
return 0;

Learning MicroStation Addins Step by Step[3]


Chapter 3. Running and debugging Addins
An Addin application is an assembly based on .NET Framework and it will be loaded into an
application domain to run. After you keyin a default (here, default means without any extra
parameter except application filename) MDL LOAD command to load an Addin, this addin will be
loaded into the default AppDomain of Mstn , this domain name is DefaultDomain. Maybe as you
know, we cant unload an assembly but we do can unload an AppDomain. So Mstn provides an
undocumented CLR (CLR means Common Language Runtime) command, it includes an unload
appdomain command. Because all assemblies of Mstn are loaded into DefaultDomain, when we load
our own assembly into non-default domain, it sometimes arises unexpected result. This is why this
CLR command undocumented. But for most of Addin applications, this unload command can work
correctly. Being able to unload an Addin can help you debug your assembly dramatically especially
for the poor performance machines because entering and exiting Mstn repeatedly will waste more
time of developers. The following step-by-step operations will illuminate what we have said.
1. Start Mstn, keyin MDL LOAD csAddins in keyin field to load our assembly. Then keyining CLR
DIALOGwill lead the .NET AppDomain Manager dialogbox. Click the DefaultDomain, you will
see the below form as shown. Please note, your csAddins assembly is just in this application domain.

2. csAddins assembly is unable to unload once it is loaded because you cant unload DefaultDomain.
At this time, when you keyin MDL LOAD csAddins again, you will see CSADDINS is already
loaded in the status bar at the left-bottom corner of Mstn window.
3. Exit Mstn and restart it again, keyin MDL LOAD csAddins,,MyDomain to load your assembly
into MyDomain application domain. Then keyin CLR DIALOG, you will see the below form and
you can find csAddins is in MyDomain application domain not in DefaultDomain.

4. Keyin CLR UNLOAD DOMAIN MyDomain to unload our assembly, delete all graphics in
active model. Then keyin MDL LOAD csAddins,,MyDomain (Actually, you can double click the
keyin you entered before to execute the command. If your command keyin window is so small that
you cant see the keyin you entered before, then you can use the up-arrow key in your keyboard to
scroll out the previous keyins), you can see the familiar graphics in active model again. This denotes
that we can run our assembly time after time without have to exit Mstn everytime.

Now that you have learned how to run Addins, lets begin the introduction of debugging Addins. We
intend to introduce two approaches of debug, they use default AppDomain and custom AppDomain
separately. Certainly, the first approach is safe and reliable for all assemblies but it needs you restart
Mstn repeatedly. The second approach is sometimes unavailable for some assemblies but it doesnt
need you restart Mstn over and over. If you have a power machine, I recommend you to use the first
approach, or you can try the second. The following steps will describe the first approach using
default application domain to debug you assembly.

1. Open your project in VS. Point Start external program to the ustation.exe of Mstn V8iSS2. You
can find this item in Debug category of your project property window. You can also keyin an valid
DGN filename in the Command line argument field, so you can enter Mstn without the display of
MicroStation Manager window. Please note, this step is belong to the configuration of your VS
project, so it is just need to do once. The below is the setting in my box. You may change the path of
ustation.exe according to your case.

2. Find the line CreateElement.LineAndLineString() in the MyAddins.cs file, click the leftmost
column of this line to set it as a breakpoint. There will be an orange-red ball at the leftmost column of
this line and the this line code will be highlighted. See the below picture:

3. Clicking the Run button (the green arrow button in the below picture) in the debug toolbar of VS
will start Mstn. Keyin MDL LOAD csAddins in the Mstns keyin field and press Enter key, you
assembly will run to breakpoint and suspend.

4. There are a lot of tools in the debug toolbar, such as the Step Into tool shown in the below red
frame and Step Over tool shown in the below blue frame. If you cant find this toolbar, please select
the menuView > Toolbars > Debug in VS. Now lets click the Step Into tool to enter the
methodLineAndLineString of CreateElement.cs.

5. When you move your mouse over a variable, the value of this variable will display in a pop-up
window, shown as below.

6. Click the Stop Debugging tool in debug toolbar will stop debug process, the Mstn session started
by VS will be also closed. There are a plenty of commands and approaches to debug your application
in VS, you can find materials related to this to learn deeply, no more tautology here.

Next we will introduce another approach to debug your application. This approach doesnt need
restarting Mstn repeatedly and doesnt need to configure the Debug page of project property at first.
Please operate following the below steps:
1. Start Mstn and this Mstn session will be used by you repeatedly.
2. Set breakpoint at the line CreateElement.LineAndLineString() in
the MyAddins.cs file. See the step 2 of the first approach for its detail.
3. Select VS menu Debug > Attach to Process, find and select ustation.exe in the pop-up
processes window (Tip: Click the title of Process column to order the processes
descendingly. Thus, ustation.exe will arise to the top of list and you can find it easily).
Then click the Attach button.

Note: If you are using VS2005, please note the Attach to option. If it isnt Managed code, you should
click the Select button to select Managed code for Addins debugging. VS2010 seems always set the
Automatic mode correctly.
4. Switch to Mstn, keyin MDL LOAD csAddins,,MyDomain and press Enter, you will enter the
debug state. The methods of debugging is same as those we have described at the first approach.
Please refer to the step 4 and 5 of the first approach.
5. When you press Stop button in debug toolbar, csAddins assembly stop running but Mstn session
will remain. If you want to do the next debug, keyin CLR UNLOAD DOMAIN MyDomain first and
repeat the step 3 to step 5 of this approach.
Learning MicroStation Addins Step by Step[4]
Chapter 4. Adding commands to Addins
If you have learnt MDL(MicroStation Development Language/Library) and MVBA(MicroStation
Visual Basic for Application) before you learn Addins, maybe you have noticed that we can use keyin
command in MDL but cant in MVBA. Although Addins look like MVBA because both of them use
the same COM library, Addins are actually loaded after they are converted to a MDL application. You
can find mdlseed.ma and mdlseed.dll under the folder \MicroStation\mdlsys\asneeded and these
files are designed for this aim. So Addins are similar to MDL and they support keyin command. With
the prevalence of XML, Addins are using XML as the format of keyin command table, not as the
Table format in MDL resource files. Introduction of the XML format of command table is the main
topic in this chapter. Embedding command table into Addin assembly and writing keyin handlers are
involved as well. Further, Using UstnXOM to verify your Addins is the last topic in this chapter.
Lets start to look at a XML command table which comes from
\MDLProjects\Dotnet\Examples\TextMod\TextMod.Commands.XML in Mstn SDK. The topmost
node in a XML command table is KeyinTree and three parts RootKeyinTable, SubKeyinTables and
KeyinHandlers are included in KeyinTree. These three parts denote the root keyin table, sub keyin
tables and the process function (i.e., handler) of every keyins separately.
<?xml version="1.0" encoding="utf-8" ?>
<KeyinTree xmlns="http://www.bentley.com/schemas/1.0/MicroStation/AddIn/KeyinTree.xsd">
<RootKeyinTable ID="root">
<Keyword SubtableRef="Commands"
CommandClass="MacroCommand" CommandWord="TEXTMOD" >
<Options Required="true"/>
</Keyword>
</RootKeyinTable>
<SubKeyinTables>
<KeyinTable ID="Commands">
<Keyword CommandWord="TRANSFORM"> </Keyword>

<Keyword SubtableRef="CaseTarget" CommandWord="CASE">


<Options TryParse="true"/> </Keyword>
</KeyinTable>
<KeyinTable ID="CaseTarget">
<Keyword CommandWord="UPPER" > <Options Default="true"/> </Keyword>
<Keyword CommandWord="LOWER" />
</KeyinTable>
</SubKeyinTables>
<KeyinHandlers>
<KeyinHandler Keyin="TEXTMOD TRANSFORM"
Function="TextMod.KeyinCommands.TransformText"/>
<KeyinHandler Keyin="TEXTMOD CASE UPPER"
Function="TextMod.KeyinCommands.ChangeToUpper"/>
<KeyinHandler Keyin="TEXTMOD CASE LOWER"
Function="TextMod.KeyinCommands.ChangeToLower"/>
</KeyinHandlers>
</KeyinTree>
There is an attribute ID, which denotes the name of the table, in <RootKeyinTable> and
<KeyinTable>. The contents of table consist of one or more <Keyword> elements and the attribute
SubtableRef of <Keyword> point to the ID of sub table. Thus, the command tree can be constructed
by using SubtableRef of <Keyword> in current table and the ID of <KeyinTable> in sub table. You
can define up to four levels command table in <SubKeyinTables> section because every Mstn
command can be constructed by one to five words. <Keyword> must have the attribute
CommandWord which is the keyin word you entered in Mstn keyin field.The attribute
CommandClass of <Keyword> denotes the command category of this keyin command and it can be
Placement, Viewing, Fence, Parameters, Locks, MacroCommand, Manipulation etc. It can also be
Inherit, means the command class of this <Keyword> is same as its parents. An <Options> item is
also included in <keyword> and it describes <Keyword> more. The attributes of <Options> can be
Required, Default, TryParse, Hidden etc. Required="true" means this command word is not the leaf
node, it must have child node. Default="true" denotes this command word is default. When you enter
a command keyin without this word, the final keyin will append this word. In one level of table, there
is only one <Keyword> can be default. For example, based on the above XML command table, when
you enter TextMod Case and press Enter TextMode Case Upper is sent to Mstn and the keyin
handler TextMod.KeyinCommands.ChangeToUpper will be called. TryParse="true" means you can
enter some extra characters or strings after this command word and these extra words are passed into
the argment "unparsed" in keyin handler. For example, you can
enter Red, Green or Blue after Active Colorcommand and you can also keyin a number after this
command, such as Active Color 245. It is impossible to list all keyins in this case, so we can use
attribute TryParse. Hidden="true" means this command is hidden, i.e., user is unable to see this
command in Mstn command browser but it is available. These hidden commands are often used by
code not for users keyin or have some other reasons.

There is only one kind of element <KeyinHandler> under the <KeyinHandlers> node. The attribute
Keyin of <KeyinHandler> denotes the full keyin command string. The attribute Function of
<KeyinHandler> denotes the handler name of this keyin command and the handler name is
constructed by namespace name, class name and function name (i.e., method name). In other words,
when user keyins the command string contained in attribute Keyin, the method in attribute Function
will be called.
Now, lets modify csAddins step by step and make it support keyin command.
1. Open your csAddins project in VS, select menu Project > Add New Item (certainly, you can do it
by right-clicking the project name and select Add New Item from the popup menu like before). Find
and select the XML File item in opened form, enter command.xml in the Name: field, then
click Add button to add an empty XML file to current project. Shown as below:

2. Copy and paste the following lines of command table to your command.xml file.
<?xml version="1.0" encoding="utf-8" ?>
<KeyinTree xmlns="http://www.bentley.com/schemas/1.0/MicroStation/AddIn/KeyinTree.xsd">
<RootKeyinTable ID="root">
<Keyword SubtableRef="CreateElement"
CommandClass="MacroCommand" CommandWord="csAddins" >
<Options Required ="true"/>
</Keyword>
</RootKeyinTable>
<SubKeyinTables>

<KeyinTable ID="CreateElement">
<Keyword SubtableRef="Commands" CommandWord="CreateElement">
<Options Required ="true"/>
</Keyword>
</KeyinTable>
<KeyinTable ID="Commands">
<Keyword CommandWord="LineAndLineString"> </Keyword>
<Keyword CommandWord="ShapeAndComplexShape"> </Keyword>
<Keyword CommandWord="TextAndTextNode"> </Keyword>
<Keyword CommandWord="CellAndSharedCell"> </Keyword>
<Keyword CommandWord="LinearAndAngularDimension"> </Keyword>
<Keyword CommandWord="CurveAndBsplineCurve"> </Keyword>
<Keyword CommandWord="ConeAndBsplineSurface"> </Keyword>
</KeyinTable>
</SubKeyinTables>
<KeyinHandlers>
<KeyinHandler Keyin="csAddins CreateElement LineAndLineString"
Function="csAddins.CreateElement.LineAndLineString"/>
<KeyinHandler Keyin="csAddins CreateElement ShapeAndComplexShape"
Function="csAddins.CreateElement.ShapeAndComplexShape"/>
<KeyinHandler Keyin="csAddins CreateElement TextAndTextNode"
Function="csAddins.CreateElement.TextAndTextNode"/>
<KeyinHandler Keyin="csAddins CreateElement CellAndSharedCell"
Function="csAddins.CreateElement.CellAndSharedCell"/>
<KeyinHandler Keyin="csAddins CreateElement LinearAndAngularDimension"
Function="csAddins.CreateElement.LinearAndAngularDimension"/>
<KeyinHandler Keyin="csAddins CreateElement CurveAndBsplineCurve"
Function="csAddins.CreateElement.CurveAndBsplineCurve"/>
<KeyinHandler Keyin="csAddins CreateElement ConeAndBsplineSurface"
Function="csAddins.CreateElement.ConeAndBsplineSurface"/>
</KeyinHandlers>
</KeyinTree>
3. Right-click command.xml file in Solution Explorer and select Properties menu to open Properties
form. Set the Build Action to Embeded Resource. Thus, command.xml file will be embedded into
csAddins.dll file. Refer to the below picture:

4. Modify MyAddins.cs in two places: 4a) Add AddInAttribute attribute definition before the line
"class MyAddin". 4b) Comment out or delete all the other lines of function Run except the last line.
Finished class should be looked like this:
[Bentley.MicroStation.AddInAttribute
(KeyinTree = "csAddins.commands.xml", MdlTaskID = "CSADDINS")]
internal sealed class MyAddin : Bentley.MicroStation.AddIn
{
private MyAddin(System.IntPtr mdlDesc)
: base(mdlDesc)
{
}
protected override int Run(string[ ] commandLine)
{
return 0;
}
}
5. Modify the declaration of every function in CreateElement.cs, make them have a string argument.
For example:
public static void LineAndLineString(string unparsed)
{

}
public static void ShapeAndComplexShape(string unparsed)
{

}
6. Build your project in VS, then switch to Mstn and load your csAddins assembly (Refer to the above
chapters for detailed steps). After you load your assembly, you cant see any elements drawn because
we have deleted all function calls in function Run of MyAddins.cs.
7. Open keyin dialogbox by selecting Mstn main menu Utilities > Key-in. When you
enter csaddins you will see a command keyin tree shown as below and this tree corresponds to the
definition in commands.xml file. At this time, when you enter a complete command and press Enter,
you will see a part of drawings displayed in the view. For example, entering csaddins createelement
coneandbsplinesurface will bring the below two 3D elements drawn in the view.

Now that you have learnt how to add commands to an Addin application, I would like to introduce a
new tool related to this topic. Due to compiler csc.exe is unable to verify the correctness of XML
command file format, Bentley provide a specific tool called UstnXOM.exe which can check the
closure of an Addin and the correctness of the definition of command table. If you have installed Mstn
SDK, you will find this tool under the folder \MicroStation\mdl\bin. Or you can get it by the
following step 1. (Note: You can download Mstn SDK from Bentley SELECTService website
http://selectservice.bentley.com. Logining into this website need you have an authorized Bentley
SELECT account). Lets start to learn how to use UstnXOM tool step by step.
1.Download UstnXOM.zip file and uncompress it to folder \MicroStation\Assemblies.
UstnXOM.zip
2.Add a new environment variable MS to point your Mstn installation folder in
Windows Environment Variables dialogbox. For me, MS point to F:\Bentley\0811\MicroStation\.
Append F:\Bentley\0811\MicroStation\Assemblies\ to your PATH environment variable. All these
settings are shown as below. Please note, dont use a path with any space characters. If your path has
any space, you can replace it with its short 8.3 format (Remark: You can enter a DOS command
DIR/X to find the short path corresponded to a long with-space path. For example, "C:\Program Files"
can be replaced by "C:\Progra~1".).

3.Open a command prompt window from Windows (this black background window is a little ugly but
very useful), change directory to your \MicroStation\mdlapps folder. Keyining UstnXOM
ValidateAddIn csAddins.dll can bring the below picture. The contents in red frame are my keyins.
You can see the prompt "No errors identified" which means your csAddins structure and the its
command table are all fine.

4. UstnXOM has some other functions, such as examining the content of the KeyinTree.xsd file. In
this case you can first enter UstnXOM DumpSchemaList to get the name of KeyinTree, it is
KeyinTree.xsd.Deflate. Then enter UstnXOM DumpSchema KeyinTree.xsd.Deflate to display the
complete KeyinTree definition file shown as below.

5. You can also add this verification program to your VS project configuration then this
verification program will be executed whenever you build your application. Do this: open the
attribute form of your csAddins project, enter $(MS)\assemblies\UstnXOM ValidateAddIn
$(TargetPath) in Post-build event command line of Build Events tab.

After this, whenever you build your project you will see the following information from the
output window of VS.

Learning MicroStation Addins Step by Step[5]


Chapter 5. Adding WinForms to Addins
We will introduce how to use modal, modeless, toolsettings and dockable dialog boxes in
Mstn. Modal, modeless and dockable dialog boxes are common in any other applications and
you can find a lot of information about them from Internet. Toolsettings dialog box is specific
to Mstn. It is a modeless dialog box and its content changes along with different tools. We can
visually design the UI of dialog box because all the dialog boxes in Addins are derived
from WinForm. This can improve your coding efficiency dramatically. The programmers who
have coded a mass of dull .r resources will enjoy the UI programming using Addins. Now
lets extend our csAddins application step by step.
1.Open the csAddins solution in VS, click the menu Project > Add Windows Form.
InputModalForm.cs in the Name field of the Add New Items form and click the Add button to
create a new WinForm. Add New Item form is shown as below picture.

2. Next we will open out the power of visualization. Clicking Toolbox icon button (or pressing
Ctrl+Alt+X when Form1 is focused) will open Toolbox window which includes a lot of controls we
can use. You can simply drag a control and drop it to Form1, then adjust its position.
Clicking Properties Window icon button (or pressing Alt+Enter when Form1 is focused) will open
properties window which includes a lot properties need us to set. Please see the below picture for
these two important windows.

3. The finished ModalForm is shown as below. We list all the properties need to set. We set the
Modifiers property of textBox1 to Public in order to access it from its parent class DemoForm. Please
note, you neednt change the Location and Size property by typing, they can be changed by visually
dragging in VS.

Form (Name) = ModalForm / AcceptButton = btnOk / ControlBox = False /


FormBorderStyle = FixedDialog
/ Size = 288,145 / Text = ModalForm
Label (Name) = label1 / Location = 22,24 / Size = 82,17 / TabIndex = 0 / Text = Your Value:
TextBox (Name) = textBox1 / Location = 111,24 / Modifiers = Public / Size = 149,22 /
TabIndex = 1
Button (Name) = btnOk / DialogResult = OK / Location = 41,71 / Size = 75,26 / TabIndex =
2 / Text = OK

Button (Name) = btnCancel / DialogResult = Cancel / Location = 157,71 / Size = 75,26 /


TabIndex = 3 / Text = Cancel
4. Now we will create a modeless dialog box. Insert a WinForm named MultiScaleCopyForm into
your csAddins project. Why we name this form to MultiScaleCopyForm? The reason is we will use it
as a toolsettings dialog box of a command in the next chapter. Drag needed controls to this form from
VSs toolbox window then set the properties of them as below.

Form (Name) = MultiScaleCopyForm / FormBorderStyle = FixedDialog / MaximizeBox =


False / MinimumBox = False
/ Size = 199,225 / Text = MultiScaleCopyForm
Label (Name) = label1 / Location = 13,17 / Size = 47,17 / TabIndex = 0 / Text = Scale:
TextBox (Name) = txtScale / Location = 81,12 / Size = 100,22 / TabIndex = 1 / Tag = 0.95 /
Text = 0.95
Label (Name) = label2 / Location = 13,45 / Size = 63,17 / TabIndex = 2 / Text = X Offset:
TextBox (Name) = txtXOffset / Location = 81,40 / Size = 100,22 / TabIndex = 3 / Tag = 4 /
Text = 4
Label (Name) = label3 / Location = 13,73 / Size = 63,17 / TabIndex = 4 / Text = Y Offset:
TextBox (Name) = txtYOffset / Location = 81,68 / Size = 100,22 / TabIndex = 5 / Tag = 0 /
Text = 0
Label (Name) = label4 / Location = 13,101 / Size = 63,17 / TabIndex = 6 / Text = Z Offset:
TextBox (Name) = txtZOffset / Location = 81,96 / Size = 100,22 / TabIndex = 7 / Tag = 0 /
Text = 0
Label (Name) = label5 / Location = 13,129 / Size = 55,17 / TabIndex = 8 / Text = Copies:
TextBox (Name) = txtCopies / Location = 81,124 / Size = 100,22 / TabIndex = 9 / Tag = 10 /
Text = 10
Button (Name) = btnDefault / Location = 40,157 / Size = 113,25 / TabIndex = 10 / Text =
Load Default
5. Add event handlers for form and controls.
a) Select MultiScaleCopyForm itself in its design window of VS (You can open form design window
by right-clicking MultiScaleCopyForm.cs in VS solution explorer and select View Designer menu). If
the properties window of VS isnt opened please open it according the step 2 of this chapter. In the
properties window, switch to the tab Events (shown as the red frame of below picture). Find
FormClosed and Load events in events list and double click them to create two event handler methods
MultiScaleCopyForm_FormClosed and MultiScaleCopyForm_Load.
b) Switch back to the design window again and select txtScale control. In the tab Events of properties
window, find and double click event KeyPress to add txtScale_KeyPress event handler method.
c) Similarly, add txtXOffset_KeyPress and txtCopies_KeyPress event handler methods.

d) Because txtYOffset and txtZOffset is similar to txtXOffset, we can share one event handler
txtXOffset_KeyPress for these three controls. Select txtYOffset control in design window and find
KeyPress event in tab Events of properties window. Dont double click the KeyPress event, or it will
create a new event handler method txtYOffset_KeyPress. Instead of double clicking, you should
select the txtXOffset_KeyPress from the drop-down list. Process the txtZOffset same as txtYOffset.
e) Double click Load Default button to add btnDefault_Click event handler method.

6. Open the source code of MultiScaleCopyForm by right-clicking MultiScaleCopyForm.cs file in VS


solution explorer and selecting View Code menu from the popup menu, then modify this code as the
below. Please note the several keypoint:
a) To access Windows register, we need to use using Microsoft.Win32;
b) In order to make a form on top of all other windows in Mstn, you should derive this form from a
Bentley defined base class ----Bentley.MicroStation.WinForms.Adapter.
c) The three KeyPress methods are used to validate the input data of Textbox controls.
d) When the user clicks the Load Default button, btnDefault_Click method copy the Tag property
value to the Text property for every TextBox control.
e) Whenever the form is closed, MultiScaleCopyForm_FormClosed method is called and it writes
current 5 values of TextBox controls into Windows register. When this form is opened next time,
MultiScaleCopyForm_Load method is called and it reads these values from Windows register and
populates them to TextBox controls.

using System;
using System.Windows.Forms;
using Microsoft.Win32;
using Bentley.MicroStation.WinForms;
namespace csAddins
{
public partial class MultiScaleCopyForm : //Form
Adapter
{
public MultiScaleCopyForm()
{
InitializeComponent();
}
private void txtScale_KeyPress(object sender, KeyPressEventArgs e)
{
if (!Char.IsDigit(e.KeyChar) && e.KeyChar != '\b' && e.KeyChar != '.')
e.Handled = true;
}
private void txtXOffset_KeyPress(object sender, KeyPressEventArgs e)
{
if (!Char.IsDigit(e.KeyChar) && e.KeyChar != '\b' && e.KeyChar != '.' && e.KeyChar != '-')
e.Handled = true;
}
private void txtCopies_KeyPress(object sender, KeyPressEventArgs e)
{
if (!Char.IsDigit(e.KeyChar) && e.KeyChar != '\b')
e.Handled = true;
}
private void btnDefault_Click(object sender, EventArgs e)
{
txtScale.Text = txtScale.Tag.ToString();
txtXOffset.Text = txtXOffset.Tag.ToString();
txtYOffset.Text = txtYOffset.Tag.ToString();

txtZOffset.Text = txtZOffset.Tag.ToString();
txtCopies.Text = txtCopies.Tag.ToString();
}
private void MultiScaleCopyForm_Load(object sender, EventArgs e)
{
RegistryKey myKey =
Registry.CurrentUser.OpenSubKey("Software\\csAddins\\MultiScaleCopy");
if (null != myKey)
{
txtScale.Text = myKey.GetValue("txtScale").ToString();
txtXOffset.Text = myKey.GetValue("txtXOffset").ToString();
txtYOffset.Text = myKey.GetValue("txtYOffset").ToString();
txtZOffset.Text = myKey.GetValue("txtZOffset").ToString();
txtCopies.Text = myKey.GetValue("txtCopies").ToString();
}
}
private void MultiScaleCopyForm_FormClosed(object sender, FormClosedEventArgs e)
{
RegistryKey rootKey = Registry.CurrentUser.OpenSubKey("Software", true);
RegistryKey appKey = rootKey.CreateSubKey("csAddins");
RegistryKey myKey = appKey.CreateSubKey ("MultiScaleCopy");
myKey.SetValue("txtScale", txtScale.Text.ToString());
myKey.SetValue("txtXOffset", txtXOffset.Text.ToString());
myKey.SetValue("txtYOffset", txtYOffset.Text.ToString());
myKey.SetValue("txtZOffset", txtZOffset.Text.ToString());
myKey.SetValue("txtCopies", txtCopies.Text.ToString());
}
}
}
Warn: After you changed your forms base class from Form to Adapter, you cant modify
your WinFrom visually. In this case, when you switch to the design window and try to edit
your WinForm you will encounter the below error message. To overcome this issue, you have
to firstly change the base class from Adapter to Form, then modify your form visually, finally
change base class back to Adapter when you finished your modification.
7. Similarly, we create NoteCoordForm as below. All the controls in this form take the standard
actions, in other words, you dont need to add any event handler to them. The only thing you should
do is to change your WinForm base class from Form to Adapter. We will use this form as a

toolsettings dialog later. There are four radio buttons in this form; they are rdoHoriz, rdoVert, rdoEN
and rdoXY. The Checked property of rdoHoriz and rdoEN are True. rdoHoriz and rdoVert are in one
group, rdoEN and rdoXY are in the other group.

Form (Name) = NoteCoordForm / FormBorderStyle = FixedDialog / MaximizeBox = False /


MinimumBox = False
/ ShowIcon = False / Size = 288,186 / Text =
NoteCoordForm
GroupBox (Name) = grpTxtDir / Location = 13,13 / Size = 257,58 / TabIndex = 0 / Text =
Text Direction
RadioButton (Name) = rdoHoriz / Checked = True / Location = 52,27 / Size = 93,21 /
TabIndex = 0
/ TabStop = True / Text = Horizontal
RadioButton (Name) = rdoVert / Location = 168,27 / Size = 76,21 / TabIndex = 1 / Text =
Vertical
GroupBox (Name) = grpLabel / Location = 15,85 / Size = 254,57 / TabIndex = 1 / Text =
Label
RadioButton (Name) = rdoEN / Checked = True / Location = 52,25 / Size = 56,21 / TabIndex
=0
/ TabStop = True / Text = EN=
RadioButton (Name) = rdoXY / Location = 168,24 / Size = 55,21 / TabIndex = 1 / Text =
XY=
8. Prepare three picture files to denote modal, modeless and toolsettings dialog box respectively. Then
open property window of your project by right-clicking your project name csAddins and selecting
theProperties item from the popup menu. Switch to the Resources page. When you ensure you have
selected the Images resource type, please click the Add Resource button to add picture resource. We
show this as below.

9. Create a new form named ToolbarForm. Add three button controls and a ToolTip control. Assign
the above three pictures to these buttons. The finished form shows as below.

Form (Name) = ToolbaForm / FormBorderStyle = FixedToolWindow / MaximizeBox = False


/ MinimumBox = False
/ ShowIcon = False / ShowInTaskbar = False / Size = 124,62 /
Text = Demo Toolbar
Button (Name) = btnModal / Image = csAddins.Properties.Resources.modal / Location =
5,3 / Size = 32,32 / TabIndex = 0
/ ToolTip on toolTip1 = Demo Modal DialogBox
Button (Name) = btnTopLevel / Image = csAddins.Properties.Resources.toplevel / Location =
43,3 / Size = 32,32
/ TabIndex = 1 / ToolTip on toolTip1 = Demo TopLevel
DialogBox
Button (Name) = btnToolSettings / Image = csAddins.Properties.Resources.tool / Location =
81,3 / Size = 32,32
/ TabIndex = 2 / ToolTip on toolTip1 = Demo ToolSettings
DialogBox
10. Double click these three buttons to construct their event handler methods. Then modify
ToolbarForm.cs source code as below. Please note two keypoints:
a) ToolbarForm derives from the base class Adapter and the interface IGuiDockable because we will
make this form dockable. The further actions to take are to implement two methods ---GetDockedExtent and WindowMoving.
b) When the user clicks the buttons, they will send command to Mstn. The implementation of
command handlers are shown at the next step.
using System;
using System.Windows.Forms;

using Bentley.MicroStation.InteropServices;
using Bentley.MicroStation.WinForms;
namespace csAddins
{
public partial class ToolbarForm : //Form
Adapter, IGuiDockable
{
private Bentley.Interop.MicroStationDGN.Application app = null;
public ToolbarForm()
{
InitializeComponent();
app = Utilities.ComApp;
}
// The below two methods come from IGuiDockable
public bool GetDockedExtent(GuiDockPosition dockPosition, ref GuiDockExtent extentFlag, ref
System.Drawing.Size dockSize)
{
return false;
}
public bool WindowMoving(WindowMovingCorner corner, ref System.Drawing.Size newSize)
{
newSize = new System.Drawing.Size(118, 34);
return true;
}
private void btnModal_Click(object sender, EventArgs e)
{
app.CadInputQueue.SendKeyin("csAddins DemoForm Modal");
}
private void btnTopLevel_Click(object sender, EventArgs e)
{
app.CadInputQueue.SendKeyin("csAddins DemoForm TopLevel");
}
private void btnToolSettings_Click(object sender, EventArgs e)
{
app.CadInputQueue.SendKeyin("csAddins DemoForm ToolSettings");

}
}
}
11. Create a new class DemoForm which file is DemoForm.cs. In this class we will process several
keyin commands and open different types of dialog boxes. For this, we call the methods
AttachAsGuiDockable, AttachAsTopLevelForm and AttachToToolSettings of Adapter.
using System;
using System.Windows.Forms;
namespace csAddins
{
class DemoForm
{
public static void Toolbar(string unparsed)
{
ToolbarForm myForm = new ToolbarForm();
myForm.AttachAsGuiDockable(MyAddin.s_addin, "toolbar");
myForm.Show();
}
public static void Modal(string unparsed)
{
ModalForm myForm = new ModalForm();
if (DialogResult.OK == myForm.ShowDialog())
MessageBox.Show(myForm.textBox1.Text.ToString());
}
public static void TopLevel(string unparsed)
{
MultiScaleCopyForm myForm = new MultiScaleCopyForm();
myForm.AttachAsTopLevelForm(MyAddin.s_addin, false);
myForm.Show();
}
public static void ToolSettings(string unparsed)
{
NoteCoordForm myForm = new NoteCoordForm();
myForm.AttachToToolSettings(MyAddin.s_addin);
myForm.Show();
}

}
}
12. Modify existing commands.xml file, Add four new commands csAddins DemoForm
Toolbar|Modal|TopLevel|ToolSettings. The new added lines are shown in the below code in brown
text.
<SubKeyinTables>
<KeyinTable ID="CreateElement">
<Keyword SubtableRef="Commands" CommandWord="CreateElement">
<Options Required="true"/>
</Keyword>
<Keyword SubtableRef="DemoForm" CommandWord="DemoForm">
<Options Required="true"/> </Keyword>
</KeyinTable>
<KeyinTable ID="Commands">

</KeyinTable>
<KeyinTable ID="DemoForm">
<Keyword CommandWord="Toolbar"/>
<Keyword CommandWord="Modal"/>
<Keyword CommandWord="TopLevel"/>
<Keyword CommandWord="ToolSettings"/>
</KeyinTable>
</SubKeyinTables>
<KeyinHandlers>

<KeyinHandler Keyin="csAddins CreateElement ConeAndBsplineSurface"


Function="csAddins.CreateElement.ConeAndBsplineSurface"/>
<KeyinHandler Keyin="csAddins DemoForm Toolbar"
Function="csAddins.DemoForm.Toolbar"/>
<KeyinHandler Keyin="csAddins DemoForm Modal" Function="csAddins.DemoForm.Modal"/>
<KeyinHandler Keyin="csAddins DemoForm TopLevel"
Function="csAddins.DemoForm.TopLevel"/>
<KeyinHandler Keyin="csAddins DemoForm ToolSettings"
Function="csAddins.DemoForm.ToolSettings"/>

</KeyinHandlers>
</KeyinTree>
13. Rebuild your project in VS. If you can build successfully at the first time, you are so luck. After
you finally generated csAddins.dll, you can load it in Mstn and keyin csAddins DemoForm
Toolbar in the Keyin field. This time you will see a toolbar window opened as the step 9 shown.
Clicking each button will open modal, modeless and toolsettings dialog box and this toolbar window
can be dockable.
Till now, the source code of our project is as below. You can download it and modify the project
properties according to your software installation location and build the project to create an
executable csAddins.dll.
http://communities.bentley.com/cfs-file.ashx/__key/CommunityServer-Blogs-ComponentsWeblogFiles/00-00-05-22-97/7536.csAddins_5F00_version01.zip
Learning MicroStation Addins Step by Step[6]
Chapter 6. Using IPrimitiveCommandEvents and ILocateCommandEvents to implement
interactive commands
You must have used the PLACE LINE and MODIFY ELEMENT command. Actually, we can
classify Mstn commands as view command and primitive command. Furthermore, primitive
command can be classified as placement command and modification command. Commands we will
implement in this chapter are placement command and modification command which need class
derived from interface IPrimitiveCommandEvents and ILocateCommandEvents separately. The first
implemented command is placing the coordinate value of user specified point dynamically and the
second is scaled-copying identified element multi-times. These two commands come from practice
and have some reference values.
To simplify our tasks, we will modify existing code based on the code of last chapter and doesnt add
new commands. Although the original command names are not very proper for our new tasks, the
irrelevancy of command name is not serious because user can start our functions by clicking icons. If
you are interesting about this, you can add two new proper commands and write two new command
handlers. Now lets start to operate step by step and show off the result of our effort.
1. Make sure you have the below two lines at the beginning of MyAddins.cs, then modify the Run
method as following. This can bring out the toolbox shown in the step 9 of last chapter as soon as you
load our assembly csAddins.dll.
using Bentley.MicroStation.InteropServices;
using BCOM = Bentley.Interop.MicroStationDGN;

protected override int Run(string[] commandLine)


{
BCOM.Application app = Utilities.ComApp;

app.CadInputQueue.SendKeyin("csAddins DemoForm Toolbar");


return 0
}
2. Open file NoteCoordForm.Desinger.cs with code mode shown as below picture. Scroll to the end of
the code; change some controls modifier from private to public. The finished code should display as
following. The reason why we make this change is we need access these public member variables out
of their classes. The value of these member variables denotes the operation result of GUI.

private System.Windows.Forms.GroupBox grpTxtDir;


public System.Windows.Forms.RadioButton rdoHoriz;
public System.Windows.Forms.RadioButton rdoVert;
private System.Windows.Forms.GroupBox grpLabel;
public System.Windows.Forms.RadioButton rdoXY;
public System.Windows.Forms.RadioButton rdoEN;
3. Similarly, modify file MultiScaleCopyForm.Designer.cs as below.
public System.Windows.Forms.TextBox txtScale;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
public System.Windows.Forms.TextBox txtXOffset;
private System.Windows.Forms.Label label3;
public System.Windows.Forms.TextBox txtYOffset;
private System.Windows.Forms.Label label4;
public System.Windows.Forms.TextBox txtZOffset;
private System.Windows.Forms.Label label5;
public System.Windows.Forms.TextBox txtCopies;
private System.Windows.Forms.Button btnDefault;
4. Now we open DemoForm.cs and do a lot of changes in it. Firstly, ensure the using statements as
below. Please note, we use using Bentley.Interop.MicroStationDGN; and using BCOM =
Bentley.Interop.MicroStationDGN; (please refer to the manual of C# language for the detailed usage
of using keyword). Why we use the second using BCOM=? The reason is the class Application and
View exist in namespace System.Windows.Forms and Bentley.Interop.MicroStationDGN
simulaneously. In this scenario, we can abbreviate Bentley.Interop.MicroStationDGN.Application and
Bentley.Interop.MicroStationDGN.View into BCOM.Applicationand BCOM.View.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Bentley.MicroStation.InteropServices;

using Bentley.Interop.MicroStationDGN;
using BCOM = Bentley.Interop.MicroStationDGN;
5. To implement the dynamic placement of identified point coordinate, we need to add a new class
into DemoForm.cs. We name this new class NoteCoordClass which derived from interface
IPrimitiveCommandEvents. The code of this class is as below. You can copy and paste it into your
DemoForm.cs or type it by yourself. This class must overwrite 6 methods: Cleanup, DataPoint,
Dynamics, Keyin, Reset and Start. Please refer to the Mstn VBA help documentation for the detailed
information of these methods. You can also get the prototype of these methods in Addins by VS
object brower shown as below picture.

class NoteCoordClass : IPrimitiveCommandEvents


{
private BCOM.Application app = Utilities.ComApp;
private NoteCoordForm myForm = new NoteCoordForm();
private Point3d[] m_atPoints = new Point3d[3];
private int m_nPoints = 0;
public void Cleanup()
{
myForm.DetachFromMicroStation();
}
public void DataPoint(ref Point3d Point, BCOM.View View)
{

if (0 == m_nPoints)
{
app.CommandState.StartDynamics();
m_atPoints[0] = Point;
m_nPoints = 1;
app.ShowPrompt("Identify note position");
}
else
{
Dynamics(ref Point, View, MsdDrawingMode.Normal);
Reset();
}
}
public void Dynamics(ref Point3d Point, BCOM.View View, MsdDrawingMode DrawMode)
{
if (1 != m_nPoints)
return;
string[] txtStr = new string[2];
Point3d[] txtPts = new Point3d[2];
Element[] elems = new Element[3];

m_atPoints[1] = Point;

txtStr[0] = (myForm.rdoEN.Checked ? "E=" : "X=") + m_atPoints[0].X.ToString("F2");


txtStr[1] = (myForm.rdoEN.Checked ? "N=" : "Y=") + m_atPoints[0].Y.ToString("F2");
double txtLen = app.ActiveSettings.TextStyle.Width * Math.Max(txtStr[0].Length,
txtStr[1].Length);
double txtLineSpacing = app.ActiveSettings.TextStyle.Height;
if (myForm.rdoHoriz.Checked)
{
m_atPoints[2].X = m_atPoints[1].X + (m_atPoints[0].X > m_atPoints[1].X ? -txtLen :
txtLen) * 1.2;
m_atPoints[2].Y = m_atPoints[1].Y;
txtPts[0].X = (m_atPoints[1].X + m_atPoints[2].X) / 2;
txtPts[0].Y = m_atPoints[1].Y + txtLineSpacing;
txtPts[1].X = txtPts[0].X;
txtPts[1].Y = m_atPoints[1].Y - txtLineSpacing;
}
else
{
m_atPoints[2].X = m_atPoints[1].X;

m_atPoints[2].Y = m_atPoints[1].Y + (m_atPoints[0].Y > m_atPoints[1].Y ? -txtLen :


txtLen) * 1.2;
txtPts[0].X = m_atPoints[1].X - txtLineSpacing;
txtPts[0].Y = (m_atPoints[1].Y + m_atPoints[2].Y) / 2;
txtPts[1].X = m_atPoints[1].X + txtLineSpacing;
txtPts[1].Y = txtPts[0].Y;
}
elems[0] = app.CreateLineElement1(null, ref m_atPoints);
elems[0].LineStyle = app.ActiveDesignFile.LineStyles.Find("0");
Matrix3d rMatrix = app.Matrix3dIdentity();
for (int i = 1; i < 3; i++)
{
elems[i] = app.CreateTextElement1(null, txtStr[i-1], ref txtPts[i-1], ref rMatrix);
elems[i].AsTextElement().TextStyle.Font =
app.ActiveDesignFile.Fonts.Find(MsdFontType.MicroStation, "ENGINEERING");
elems[i].AsTextElement().TextStyle.Justification = MsdTextJustification.CenterCenter;
if (myForm.rdoVert.Checked)
elems[i].RotateAboutZ(txtPts[i-1], Math.PI / 2);
}
CellElement elemCell = app.CreateCellElement1("NoteCoordCell", ref elems, ref
m_atPoints[0]);
elemCell.Redraw(DrawMode);
if (MsdDrawingMode.Normal == DrawMode)
app.ActiveModelReference.AddElement(elemCell);
}
public void Keyin(string Keyin)
{
}
public void Reset()
{
m_nPoints = 0;
app.CommandState.StartPrimitive(this);
}
public void Start()
{
myForm.AttachToToolSettings(MyAddin.s_addin);
myForm.Show();
app.ShowCommand ("Note Coordinate");

app.ShowPrompt("Please identify a point");


app.CommandState.EnableAccuSnap();
}
}
We load and show myForm into ToolSettings dialog in Start method of NoteCoordClass by calling
AttachToToolSettings and Show method. When the command ends, we unload myForm in Cleanup
method of NoteCoordClass by calling DetachFromMicroStation method. This process overcomes the
shortcomings at the last chapter, in that, after myForm is loaded into ToolSettings it cant be
dismissed except you close the ToolSettings dialog. Maybe you want to know when the current
command end (e.g. when the Cleanup method is called). The answer is when the other same-level
command start. The level of primitive command is higher than view command. The level of
placement command and modification command in primitive command are the same. If you insert a
view command during the execution of a primitive command the primitive command doesnt end but
suspend, after the end of view command you can resume the primitive command by clicking the reset
button (default is the right button of your mouse).
6. Next we will implement the multiple scaled-copy command. We need add a new class derived from
ILocateCommandEvents into DemoForm.cs as below. This class must overwrite 6 methods: Accept,
Cleanup, Dynamics, LocateFailed, LocateReset and Start.
class MultiScaleCopyClass : ILocateCommandEvents
{
private BCOM.Application app = Utilities.ComApp;
private MultiScaleCopyForm myForm = new MultiScaleCopyForm();
public void Accept(Element Elem, ref Point3d Point, BCOM.View View)
{
Element newEl;
Point3d orgPnt;
double dScale = double.Parse(myForm.txtScale.Text);
Point3d offsetPnt = app.Point3dFromXYZ(double.Parse(myForm.txtXOffset.Text),
double.Parse(myForm.txtYOffset.Text),
double.Parse(myForm.txtZOffset.Text));
for (int i = 0; i < int.Parse(myForm.txtCopies.Text); i++)
{
newEl = app.ActiveModelReference.CopyElement(Elem);
newEl.Move(ref offsetPnt);
orgPnt.X = (newEl.Range.Low.X + newEl.Range.High.X) * 0.5;

orgPnt.Y = (newEl.Range.Low.Y + newEl.Range.High.Y) * 0.5;


orgPnt.Z = (newEl.Range.Low.Z + newEl.Range.High.Z) * 0.5;
newEl.ScaleAll(ref orgPnt, dScale, dScale, dScale);
newEl.Redraw(MsdDrawingMode.Normal);
Elem = newEl.Clone();
}
}
public void Cleanup()
{
myForm.DetachFromMicroStation();
}
public void Dynamics(ref Point3d Point, BCOM.View View, MsdDrawingMode DrawMode)
{
}
public void LocateFailed()
{
app.CommandState.StartLocate(this);
}
public void LocateFilter(Element Element, ref Point3d Point, ref bool Accepted)
{
}
public void LocateReset()
{
}
public void Start()
{
myForm.AttachToToolSettings(MyAddin.s_addin);
myForm.Show();
app.ShowCommand("MultiScaleCopy");
app.ShowPrompt("Please identify an element");
app.CommandState.EnableAccuSnap();
}
}
7. Finally, lets modify the TopLevel and ToolSettings method of DemoFrom class as below. The
TopLevel method is used to start multiple scaled-copy command and the ToolSettings method is used
to start note coordinate command. They call the StartLcate and StartPrimitive method of

CommandState object separately. A remark: the command of note coordinate is limited to work in 2D
model and ignore the annotation scale of text.
public static void TopLevel(string unparsed)
{
Utilities.ComApp.CommandState.StartLocate(new MultiScaleCopyClass());
}
public static void ToolSettings(string unparsed)
{
BCOM.Application app = Utilities.ComApp;
if (app.ActiveModelReference.Is3D)
{
MessageBox.Show("This tool can only work in 2D model");
return;
}
app.ActiveSettings.AnnotationScaleEnabled = false;
app.CommandState.StartPrimitive(new NoteCoordClass());
}
8. Build your csAddins and test it. You can firstly place a square in a 2D design model, then load
csAddins and click the third tool in our csAddins toolbox. By selecting a corner of this square you
will see a note line and two coordinate texts displayed dynamically. Identify another point to place the
note into design model. The two notes are shown as below picture

.
Click the second tool and set the parameters as the following dialog shown. Select this square and
accept, you will see the below created drawings.

Actually, Mstn has provided us the approach to customize GUI without programming. The
customized GUI can be saved in a file which extension is .DGNLIB. If this DGNLIB file is located
under the folders pointed by configuration variable MS_DGNLIBLIST or MS_GUIDGNLIBLIST, it
will be loaded when Mstn starts. The customization work can be done by opening this DGNLIB file
and selecting the main menu Workspace > Customize. We dont dig into this topic deeply because
this blog series arent focused on the customization of Mstn. The following pictures are the
customization graphic interface and the result of my customization.

You can download the below attachment and put it under a folder which is contained in your
MS_DGNLIBLIST. Restart your Mstn then you will see the above effect.
csAddins.DGNLIB

Learning MicroStation Addins Step by Step[7]


We have captured the data and reset button events (certainly we can also capture the keyin event)
using IPrimitiveCommandEvents and ILocateCommandEvents in the last chapter. Both of them
are events occurred in Mstn. In fact, there are many events in Mstn we can capture. The following
table lists the normal events and their event handlers.
Type of Events

Event Handlers

Reference File Attachments

The methods of IAttachmentEvents

Raster File Attachments

The methods of IRasterEvents

Design File Opened or Closed

NewDesignFileEventHandler (append to NewDesignFileEvent)

SaveAs Command

The methods of ISaveAsEvents

Model Activation

The methods of IModelActivateEvents

Tracking Element Changes

The methods of IChangeTrackEvents

View Update

The methods of IViewUpdateEvents

Level Change

The methods of ILevelChangeEvents

Keyin, Datapoint, Reset

The methods of IPrimitiveCommandEvents and ILocateCommandEvents

Modal Dialog Opened or Closed

The methods of IModalDialogEvents

In this chapter, we will demonstrate the usage of ILevelChangeEvents and


NewDesignFileEventHandler by an example. This example will display the levels of active design file
in a dialogbox. When you add, remove or rename levels in Mstns Level Manager the level
information in our dialogbox will change automatically. Now lets construct this function step by step.
1. Create a new WinForm in VS and name it LevelChangedForm. Then add a ListBox control to this
form. The finished form is shown as below:

2. Set the properties of this form as following. Please note: we set the Modifiers property of ListBox
to Public which let us need not to change the source code of LevelChangedForm.Designer.cs directly
like we do in the last chapter. Setting the Sorted property of ListBox to True makes it displays its
content in alphabetic order.
Form (Name) = LevelChangedForm / FormBorderStyle = FixedDialog / MaximizeBox = False /
MinimumBox = False
/ ShowIcon = False / Size = 288,423 / Text = LevelChangedForm
ListBox (Name) = listBox1 / Location = 5,13 / Modifiers = Public / Size = 272,372 / Sorted = True
3. Open LevelChangedForm.cs by 'view code' mode and modify it. The final version of this file
should look like as below. Some keypoints we need to emphasize here. a). Modify the base class of
LevelChangedForm from Form to Adapter. b). When this form is opened, LevelChangedForm_load
method will be called. In this method, we add DGN files levels to ListBox by calling
listBox1.Items.Add method. c). When this form is closed, LevelChangedForm_FormClosed method is
called. In this method, we remove two event handlers which we will describe in detail.
using System;
using System.Windows.Forms;
using Bentley.MicroStation.WinForms;
using Bentley.MicroStation.InteropServices;
using Bentley.Interop.MicroStationDGN;
using BCOM = Bentley.Interop.MicroStationDGN;namespace csAddins
{

public partial class LevelChangedForm : Adapter


{
private BCOM.Application app = Utilities.ComApp;
public LevelChangedForm()
{
InitializeComponent();
}
private void LevelChangedForm_Load(object sender, EventArgs e)
{
foreach (Level myLvl in app.ActiveDesignFile.Levels)
listBox1.Items.Add(myLvl.Name);
}
private void LevelChangedForm_FormClosed(object sender, FormClosedEventArgs e)
{
app.RemoveLevelChangeEventsHandler(DemoForm.myLevelChanged);
MyAddin.s_addin.NewDesignFileEvent -= DemoForm.myNewDGNHandler;
}
}
}
4. Open the file command.xml, add a new command csAddins DemoForm LevelChanged and
identify its handler to csAddins.DemoForm.LevelChanged. If you are not familiar with the XML
command table, please refer the related topics in chapter 4.
5. Open the file DemoForm.cs and scroll to its end. Then add command handler LevelChanged as
below. This snippet implements three things: a). Open LevelChangedForm in singleton mode. In other
words, this form can only has one instance at the same time. b). Add level change handler by using
AddLevelChangeEventshandler. c). Append our new design file event handler(myNewDGNHandler)
to NewDesignFileEvent chain of current Addin instance.
public static LevelChangedClass myLevelChanged = null;
public static AddIn.NewDesignFileEventHandler myNewDGNHandler = null;
private static LevelChangedForm myLevelForm = null;
public static void LevelChanged(string unparsed)
{
if (null == myLevelForm || myLevelForm.IsDisposed)
{
myLevelForm = new LevelChangedForm();
myLevelForm.AttachAsTopLevelForm(MyAddin.s_addin, false);
myLevelForm.Show();
myLevelChanged = new LevelChangedClass();
Utilities.ComApp.AddLevelChangeEventsHandler(myLevelChanged);
myNewD
GNHandler = new AddIn.NewDesignFileEventHandler
(LevelChangedClass.MyAddin_NewDesignFileEvent);
MyAddin.s_addin.NewDesignFileEvent += myNewDGNHandler;
}
else
myLevelForm.Activate();
}
6. Next we will construct LevelChangedClass. We still add code into DemoForm.cs. We should
notice the following points to this code snippet: a). This class should be derived from
interface ILevelChangeEvents. b). Need to rewrite a unique method LevelChanged. In this method,
we update the content of listBox1 in three cases. c). In order to call the common method

PopulateLevelList, we put the NewDesignFile event handler into this class. This is not necessary.
Please note: The method MyAdd_NewDesignFileEvent is named by us but its prototype must
conform to the delegate declaration we found from VS object browser shown as below picture. d).
The method PopulateLevelList is defined by us. By iterating the collection
System.Windows.Forms.Application.OpenForms, we can find the opened LevelChangedForm and
populate it again.

class LevelChangedClass : ILevelChangeEvents


{
public void LevelChanged(MsdLevelChangeType ChangeType, Level TheLevel,
ModelReference TheModel)
{
if (MsdLevelChangeType.AfterCreate == ChangeType ||
MsdLevelChangeType.AfterDelete == ChangeType ||
MsdLevelChangeType.ChangeName == ChangeType)
PopulateLevelList();
}
public static void MyAddin_NewDesignFileEvent
(AddIn sender, AddIn.NewDesignFileEventArgs eventArgs)
{
if (AddIn.NewDesignFileEventArgs.When.AfterDesignFileOpen == eventArgs.WhenCode)
PopulateLevelList();
}
private static void PopulateLevelList()
{
LevelChangedForm myLevelChangedForm = null;
foreach (Form myForm in System.Windows.Forms.Application.OpenForms)
if ("LevelChangedForm" == myForm.Name)
{
myLevelChangedForm = (LevelChangedForm)myForm;
break;
}
if (null != myLevelChangedForm)
{
myLevelChangedForm.listBox1.Items.Clear();
foreach (Level myLvl in Utilities.ComApp.ActiveDesignFile.Levels)
myLevelChangedForm.listBox1.Items.Add(myLvl.Name);

}
}
}
7. Lets test our assembly after finishing the code writing. Firstly, build csAddins again from VS.
Then start Mstn and keyin mdl load csAddins,,MyDomain to load csAddins. Open LevelChanged
form by keyining csaddins demoform levelchanged and open Mstns Level Manager by selecting
the menuSettings > Levels > Manager. The two opened dialogbox are shown as below. When you
now add, remove a level or change a levels name, the content of LevelChanged form updates
immediately. You can also test the file opening action. You will find the content of LevelChanged
form updated automatically after you open a new design file.

Learning MicroStation Addins Step by Step[8]


Chapter 8. Calling MDL API function from Addins
Maybe you have noticed that neither mesh surface nor SmartSolid can be found in our
MicroStationDGN object model. In fact, these two important 3D functions havent wrapped into
current object model as of MstnV8iSS2. Then does it say we can not implement these advanced
functions by C# language? The answer is NO. By PInvoke of C# we can call functions in Win32 API
and by the same approach we can call functions in MDL API.
I strongly recommend you to install Mstn V8iSS2 SDK before you learn the example of this chapter.
You can download this development kit from website http://selectservices.bentley.com shown as
below. After you install this kit you will find three chm format help documents under the folder
documentation of your Mstn. One of them is MDLAPIFunctionReference.chm in which all of
published MDL functions are listed and you can find the prototype of
mdlMesh_newPolyfaceFromXYTriangulation from it. Now lets start to create a Mesh surface
element from C# addin step by step.

1. Open the file command.xml, add a new command csAddins CreateElement Mesh and identify its
handler to csAddins.CreateElement.Mesh. If you are not familiar with the XML command table,
please refer the related topics in chapter 4.
2. Open the file CreateElement.cs and add three using statements at the beginning of its code. These
using statements are used for file processing, generic programming and interop.
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
3. Scroll to the end of this file; add command handler Mesh and DllImport attribute delaration which
PInvoke needed. The finished code is shown as below. We need to point out : a). The prototype of
mdlMesh_newPolyfaceFromXYTriangulation can be found from MDLAPIFunctionReference.chm;
b). The data this function needed comes from an external text file named data-13.asc. We use a .NET
class StreamReader to implement file reading; c). Because the amout of data points is unpredicated,
we have to use a generic programming collection class List which begins from C# 2.0 to dynamically
get these points data; d). We also call the important method MdlCreateElementFromElementDescrP
to convert the MDL element descriptor ppMeshDescr returned by
mdlMesh_newPolyfaceFromXYTriangulation to an element meshEl in object model.
[DllImport("stdbspline.dll")]
public static extern int mdlMesh_newPolyfaceFromXYTriangulation
(out int ppMeshDescr, Point3d[] xyzArray, int numXYZ);
public static void Mesh(string unparsed)
{
Application app = Utilities.ComApp;
char[] delimiterChars = { ' ' };
string myLine;
List<Point3d> meshPnts = new List<Point3d>();
StreamReader sr = new StreamReader(@"d:\data-13.asc");
while (null != (myLine = sr.ReadLine()))
{
string[] sArray = myLine.Split(delimiterChars);

meshPnts.Add(app.Point3dFromXYZ(
double.Parse(sArray[0]), double.Parse(sArray[1]), double.Parse(sArray[2])));
}
sr.Close();
int ppMeshDescr;
Point3d[] xyzArray = meshPnts.ToArray();
if (0 == mdlMesh_newPolyfaceFromXYTriangulation(out ppMeshDescr, xyzArray,
meshPnts.Count))
{
Element meshEl = app.MdlCreateElementFromElementDescrP(ppMeshDescr);
app.ActiveModelReference.AddElement(meshEl);
}
}
4. Please download data-13.zip file and uncompress it to your root folder of drive D. Certainly, this
data file can be placed at any other position as long as you keep the same position in your source
code.
data-13.zip
5. Build csAddins in VS; Start Mstn and open a 3D model; Keyin mdl load csAddins,,MyDomain to
load csAddins and then continue to keyin csaddins createelement mesh to generate a mesh surface
shown as below. Its render effect is also shown.

The below link is the final version of csAddins source code, for your reference.
csAddins-Final.zip
Learning MicroStation Addins Step by Step[9]
Chapter 9. Writing Addins using C++/CLI
From the last chapter, maybe you have found that in order to call the MDL API function we have to
declare it by using DllImport attribute. Fortunately, the function
mdlMesh_newPolyfaceFromXYTriangulation is simple and we just need one such function to create a
mesh surface. But if you want to create smart solid by programming, you will have no luck because it
normally needs to call several MDL functions. In this case, it will be cumbersome and difficult to
declare every function by DllImport attributes. Luckily Microsoft provides us a powerful mixed
assembly programming language, it is C++/CLI. When we change to write our assembly with
C++/CLI, it is possible to use .NET functionality and to call MDL functions directly. Now, lets write
an Addin application by using C++/CLI. We will demonstrate creating a mesh surface and a smart
solid.
1. Open our csAddins solution in VS and open the Create New Project form by selecting menu File >
New > Project. Then add a new project cppAddins according to the below picture. Please note, the
type of new project is Class Library under the category Visual C++/CLR and the Solution option
should be selected as Add to solution.

2. Select solution csAddins (not the project csAddins), then press the function key <F2> to rename the
csAddins to mstnAddins. Exit VS and rename the folder name csAddins to mstnAddins in Windows
explorer. Restart VS, open the new mstnAddins solution. Now you should see two projects cppAddins
and csAddins in the solution mstnAddins shown as below:

3. Make sure you have a correct MS variable definition in you system environment because our
following settings will heavily depend on this environment variable. This variable should point to the
installation location of your MstnV8iSS2. The below picture shows the case in my box. If you havent
this setting, please exit VS, add this new environment variable followed by the below picture which
you can begin from the property of "My Computer" and then restart VS. Why we need to exit and
restart VS? Because VS searches system environment variables only at its starting time.

4. Next we will set the default path of C++ project. This step is needed to do only once and all C++
projects will inherit these settings. Open property manager by selecting menu View > Property
Manager, expand cppAddins and Release|Win32 (or Debug|Win32), then double click
Microsoft.Cpp.Win32.user to open the project default property page dialogbox. Select the VC++
Directories on the left pane of this dialogbox, and then set the Include Directories, Reference
Directories and Library Directories on the right pane like the following picture shown.

5. Set the project property of cppAddins. Open the project property dialogbox by right clicking the
cppAddins and selecting the Properties menu item on the popup menu. Set Output Directory to
$(MS)mdlapps\ under the category General. This makes the cppAddins.dll is created under the
mdlapps folder of Mstn. Set Command Line to $(MS)assemblies\UstnXOM ValidateAddIn
$(TargetPath) under the Post-Build Event of Build Events (Certainly, you can set Command Line to
$(MS)mdl\bin\UstnXOM ValidateAddIn $(TargetPath) if you have installed Mstn SDK). This setting
makes the assembly validation check after finishing the project build. Please refer to chapter 4 for the
detailed description of assemblys validation.

6. Copy file commands.xml from project csAddins to cppAddins under Windows explorer. Back to
VS, add commands.xml under the Resource Files of cppAddins. Open this files property page, set its
Item Type to Compiled Managed Resource. This is very important because it can make
commands.xml embedded into the final assembly cppAddins.dll as resource.

7. Double click commands.xml to open it, and then edit it. The two commands we created are
cppAddins CreateElement Mesh and cppAddins CreateElement Solid. The final edition of this file is
as below:
<?xml version="1.0" encoding="utf-8" ?>
<KeyinTree xmlns="http://www.bentley.com/schemas/1.0/MicroStation/AddIn/KeyinTree.xsd">
<RootKeyinTable ID="root">
<Keyword SubtableRef="CreateElement"
CommandClass="MacroCommand" CommandWord="cppAddins" >
<Options Required="true"/>
</Keyword>
</RootKeyinTable> <SubKeyinTables>
<KeyinTable ID="CreateElement">
<Keyword SubtableRef="Commands" CommandWord="CreateElement">
<Options Required="true"/>
</Keyword>
</KeyinTable>
<KeyinTable ID="Commands">
<Keyword CommandWord="Mesh"> </Keyword>
<Keyword CommandWord="Solid"> </Keyword>
</KeyinTable>
</SubKeyinTables> <KeyinHandlers>
<KeyinHandler Keyin="cppAddins CreateElement Mesh"
Function="cppAddins.CreateElement.Mesh"/>
<KeyinHandler Keyin="cppAddins CreateElement Solid"
Function="cppAddins.CreateElement.Solid"/>

</KeyinHandlers>
</KeyinTree>
8. Double click the file stdafx.h under Header Files to open it, and then edit it as below. To call MDL
functions, we define winNT and include one header file (.h) and three function definition file (.fdf).
To construct Mstn Addin assembly with C++, we use #using directive to reference five necessary
DLLs. Meantime, we use using namespace declaration to declare namespace std and
System::IO. Special emphasis here, we eliminate the line using namespace System because it will
bring ambiguous symbol error when you try to use UInt32 type in your program. UInt32 is a global
type definition in MDL, it conflicts with the System::UInt32 when we omit the System namespace.
#pragma once
#define winNT 1
#include <MicroStationAPI.h>
#include <mselmdsc.fdf>
#include <msmdlmesh.fdf>
#include <mskisolid.fdf>
#using <System.dll>
#using <ustation.dll>
#using <Bentley.MicroStation.dll>
#using <Bentley.MicroStation.Interfaces.1.0.dll>
#using <Bentley.General.1.0.dll>
using namespace std;
using namespace System::IO;
9. Double click the file stdafx.cpp under Source Files to open it, and then edit is as below. In this file,
we use pragma comment to identify the static libraries we need to link. Certainly, you can also
specify these library files under the Linker category of project property.
#include "stdafx.h"
#pragma comment(lib, "mdlbltin.lib")
#pragma comment(lib, "BentleyDgn.lib")
#pragma comment(lib, "toolsubs.lib")
#pragma comment(lib, "mdllib.lib")
#pragma comment(lib, "msbspline.lib")
#pragma comment(lib, "kisolid.lib")

10. Double click the file cppAddins.cpp under Source Files to open it, and then edit is as below. This
file is the main framework of Mstn Addin assembly. Please refer to the previous related topics of
writing Mstn Addin assembly by using C# to understand their meanings.
#include "stdafx.h"
namespace cppAddins
{
typedef Bentley::MicroStation::AddIn Base;
[Bentley::MicroStation::AddInAttribute(KeyinTree="commands.xml", MdlTaskID="CppAddins")]
public ref class MyAddin sealed : public Base
{
private: MyAddin(System::IntPtr mdlDesc) : Base(mdlDesc)
{
}
public: virtual int Run(array<System::String^>^ commandLine) override
{
return 0;
}
};
}
11. Right click Source Files under the project cppAddins, select Add > New Item in the popup menu,
and then select file type is C++ File(.cpp) in the new popup form and input file name CreateElement.
Click Ok button to add a new source file CreateElement.cpp under the Source Files. This is our main
work file.
12. Input the following contents in CreateElement.cpp. We explain several keypoints here: a) We
define two command handlers Mesh and Solid to process command cppAddins CreateElement Mesh
and cppAddins CreateElement Solid. b) In Mesh function, we change to use C++STL vector to store
datapoints which come from data file data-13.asc dynamically. And we use mdlCnv_
masterUnitsToUors to convert datapoints from master unit to resolution unit because the default unit
in MDL function is resolution unit not master unit. (If you arent familiar with the concept of working
unit please to learn it from the help documentation of Mstn). c) Please note, we use a lot of MDL
functions, such as mdlCnv_ masterUnitsToUors, mdlMesh_newPolyfaceFromXYTriangulation,
mdlElmdscr_add and mdlKISolid_xxx. We dont need to declare them with DllImport attributes and
this is just the main reason we change to C++/CLI. d) In order to call mdlKISolid_sweepBodyWire,
we have to call several extra MDL functions in Solid function. We firstly create a linestring as path,
and then create a circle as profile. By sweeping the profile along with the path, we create a 3D pipe
which type is smart solid (type=2) not solid (type=19). Detailed explanation of KISolid functions is
out of the range of this blog. If you are interested in the code and have some doubts, welcome to
contact me or write them directly in the comments of this blog.
#include "stdafx.h"
namespace cppAddins
{
public ref class CreateElement

{
public:
static void Mesh (System::String^ unparsed)
{
DPoint3d pt, *pPt = NULL, *xyzArray = NULL;
vector <DPoint3d> meshPnts;
array<System::Char>^ delimiterChars = { ' ' };
System::String^ myLine;
StreamReader^ sr = gcnew StreamReader("d:\\atemp\\data-13.asc");
while (nullptr != (myLine = sr->ReadLine()))
{
array<System::String^>^ sArray = myLine->Split(delimiterChars);
pt.x = mdlCnv_masterUnitsToUors (System::Double::Parse(sArray[0]));
pt.y = mdlCnv_masterUnitsToUors (System::Double::Parse(sArray[1]));
pt.z = mdlCnv_masterUnitsToUors (System::Double::Parse(sArray[2]));
meshPnts.push_back (pt);
}
sr->Close();
xyzArray = new DPoint3d[meshPnts.size()];
pPt = xyzArray;
for (vector<DPoint3d>::iterator iter=meshPnts.begin(); iter != meshPnts.end(); ++iter,++pPt)
*pPt = *iter;
MSElementDescrP pMeshDescr = NULL;
if (SUCCESS == mdlMesh_newPolyfaceFromXYTriangulation(&pMeshDescr, xyzArray,
meshPnts.size()))
{
mdlElmdscr_add (pMeshDescr);
mdlElmdscr_freeAll (&pMeshDescr);
}
delete[] xyzArray;
}
static void Solid (System::String^ unparsed)
{
MSElement

el;

MSElementDescrP pPathDescr = NULL, pProfileDescr = NULL, pSolidDescr = NULL;


KIBODY

*pPathBody = NULL, *pProfileBody = NULL;

Transform

fwdBodyTransform, tmpBodyTransform, toolTransform;

RotMatrix

rMatrix;

DPoint3d

pts[4] = {{0,0,0}, {5000,0,0}, {5000,5000,0}, {5000,5000,5000}};

DVec3d

zVec;

mdlLineString_create (&el, NULL, pts, 4);

mdlElmdscr_new (&pPathDescr, NULL, &el);

mdlVec_subtractDPoint3dDPoint3d

(&zVec, &pts[1], &pts[0]);


mdlVec_normalizeDVec3d (&zVec, &zVec);
mdlRMatrix_fromNormalVector (&rMatrix, &zVec);
mdlRMatrix_getInverse (&rMatrix, &rMatrix);
mdlEllipse_create (&el, NULL, pts, 200, 200, &rMatrix, 0);
mdlElmdscr_new (&pProfileDescr, NULL, &el);
if (SUCCESS == mdlKISolid_elementToBody2 (&pPathBody, &fwdBodyTransform,
pPathDescr, ACTIVEMODEL, 1L, FALSE) &&
SUCCESS == mdlKISolid_elementToBody2 (&pProfileBody, &tmpBodyTransform,
pProfileDescr, ACTIVEMODEL, 1L, FALSE)
&& NULL != pPathBody && NULL != pProfileBody)
{
Transform invBodyTransform;
mdlTMatrix_getInverse (&invBodyTransform, &fwdBodyTransform);
mdlTMatrix_multiply (&toolTransform, &invBodyTransform, &tmpBodyTransform);
mdlKISolid_applyTransform (pProfileBody, &toolTransform);
if (SUCCESS == mdlKISolid_sweepBodyWire (&pProfileBody, pPathBody, NULL, 0, true,
ACTIVEMODEL))
{
if (SUCCESS == mdlKISolid_bodyToElement (&pSolidDescr, pProfileBody, -1, -1, NULL,
ACTIVEMODEL))
{
mdlKISolid_beginCurrTrans (ACTIVEMODEL);
mdlElmdscr_transform (pSolidDescr, &fwdBodyTransform);
mdlKISolid_endCurrTrans ();
mdlElmdscr_add (pSolidDescr);
mdlElmdscr_freeAll (&pSolidDescr);
}
}
}
if (NULL != pProfileBody) mdlKISolid_freeBody (pProfileBody);
if (NULL != pPathBody) mdlKISolid_freeBody (pPathBody);
if (NULL != pPathDescr) mdlElmdscr_freeAll (&pPathDescr);

if (NULL != pProfileDescr) mdlElmdscr_freeAll (&pProfileDescr);


}
};
};
13. Build and test cppAddins assembly. Right click cppAddins project and select Build menu item in
the popup menu to create cppAddins.dll. Or you can simple click the first icon tool in the Build
toolbox to create cppAddins.dll. After you have successfully created the assembly, you can start Mstn
and open a DGN with 3D model. Next, keyin MDL LOAD cppAddins to load cppAddins assembly.
And then, keyin cppAddins CreateElement Mesh to draw a mesh surface which is same as the surface
drawn in last chapter. Finally, keyin cppAddins CreateElement Solid to draw a 3D pipe shown as
below pictures.

Thus far, we finished our Learning MicroStation Addins Step by Step series. The final source code
of solution mstnAddins is as below. Warmly welcome to identify my mistakes and warmly welcome
to share this blog to other people.
mstnAddins.zip

You might also like