Chapter 4: Adding Commands to Add-ins


 

In this chapter, we will explore how command tables are defined using XML, how the XML command table is embedded within an Add-in, and how the corresponding command handlers are implemented in code. By the end of this chapter, you will be able to add your own custom key-ins to a MicroStation Add-in and connect them to application functionality.

Unlike traditional MDL applications, where command tables were typically defined in resource files, Add-ins use an XML-based command table. This approach makes command definitions easier to read, modify, and maintain. Understanding the structure of this XML file is an essential step when developing Add-ins that support custom commands.

To illustrate these concepts, we will use the following commands.xml file from the delivered SDK ManagedFenceExample

C:\Program Files\Bentley\MicroStation2026SDK\examples\Elements\ManagedFenceExample\commands.xml 

 

<?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" CommandWord="FENCEEXAMPLE">
            <Options Required="true" />
        </Keyword>
    </RootKeyinTable>

    <SubKeyinTables>
        <KeyinTable ID="Commands">            
            <Keyword SubtableRef="FromCmd" CommandWord="FROM">
                <Options Required="true" />
            </Keyword>
          <Keyword SubtableRef="ModifyCmd" CommandWord="MODIFY">
            <Options Required="true" />
          </Keyword>
          <Keyword CommandWord="CLEAR">
            <Options Required="true" />
          </Keyword>
        </KeyinTable>

        <KeyinTable ID="FromCmd">
          <Keyword CommandWord="ELEMENT" />
          <Keyword CommandWord="POINTS" />
        </KeyinTable>

      <KeyinTable ID="ModifyCmd">
        <Keyword CommandWord="MOVE"/>
        <Keyword CommandWord="CLIP"/>
        <Keyword CommandWord="STRETCH"/>
      </KeyinTable>

    </SubKeyinTables>

    <KeyinHandlers>
        <KeyinHandler Keyin="FENCEEXAMPLE FROM ELEMENT" 
                      Function="ManagedFenceExample.Keyin.CmdPlaceFenceFromElement"/>
        <KeyinHandler Keyin="FENCEEXAMPLE FROM POINTS" 
                      Function="ManagedFenceExample.Keyin.CmdPlaceFenceFromPoints"/>
        <KeyinHandler Keyin="FENCEEXAMPLE CLEAR" 
                      Function="ManagedFenceExample.Keyin.CmdClearFence"/>
        <KeyinHandler Keyin="FENCEEXAMPLE MODIFY MOVE" 
                      Function="ManagedFenceExample.Keyin.CmdMoveFenceContents"/>
        <KeyinHandler Keyin="FENCEEXAMPLE MODIFY CLIP" 
                      Function="ManagedFenceExample.Keyin.CmdClipFenceContents"/>
        <KeyinHandler Keyin="FENCEEXAMPLE MODIFY STRETCH" 
                      Function="ManagedFenceExample.Keyin.CmdStretchFenceContents"/>      
    </KeyinHandlers>

</KeyinTree>

 

The commands.xml file tells MicroStation:

  1. Which key-in commands are available
  2. How those commands are organized
  3. Which function to run when a command is entered

Think of it as a menu system. Each command word leads to the next available command word until a complete command is formed.

For example:

FENCEEXAMPLE FROM ELEMENT
FENCEEXAMPLE FROM POINTS
FENCEEXAMPLE MODIFY MOVE
FENCEEXAMPLE MODIFY CLIP
FENCEEXAMPLE MODIFY STRETCH
FENCEEXAMPLE CLEAR

 

These commands can be visualized as a tree:

FENCEEXAMPLE
│─── FROM
│        │─── ELEMENT
│        └─── POINTS
│─── MODIFY
│        │─── MOVE
│        │─── CLIP
│        └─── STRETCH
└─── CLEAR

 

The XML command table is organized around a single KeyinTree node, which serves as the root of the command hierarchy. Every command table contains one and only one KeyinTree element. Within this node are three main sections:

                                 MicroStation calls function, ManagedFenceExample.Keyin.CmdMoveFenceContents

In simple terms, the command tree defines what users can type, while the KeyinHandlers section defines what happens when they type it.

 

 

Step-by-step process demonstrating how to add Commands to Add-ins.

 

  1. Create a new Project

 

Begin by creating a new Add-in project by following the steps described in the previous section, Building a Simple Add-in Application, name the new project csAddins4

 

If you have already created a Visual Studio project template from the previous section, you can use that template to quickly generate a new project. Simply create a new project from the template and assign it the name csAddins4.

 

 

  1. Create a new XML file

Copy and paste the following lines into the commands.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="csAddins4" >
			<Options Required="true"/>
		</Keyword>
	</RootKeyinTable>

	<SubKeyinTables>
		<KeyinTable ID="CreateElement">
			<Keyword SubtableRef="Commands" CommandWord="CreateElement">
				<Options Required="true"/>
			</Keyword>
		</KeyinTable>
		<KeyinTable ID="Commands">
			<Keyword CommandWord="LineAndLineString1"> </Keyword>
			<Keyword CommandWord="LineAndLineString2"> </Keyword>
			<Keyword CommandWord="LineAndLineString3"> </Keyword>
			<Keyword CommandWord="ShapeAndComplexShape"> </Keyword>
			<Keyword CommandWord="TextString"> </Keyword>
			<Keyword CommandWord="Cell"> </Keyword>
			<Keyword CommandWord="Dimension"> </Keyword>
			<Keyword CommandWord="BsplineCurve"> </Keyword>
			<Keyword CommandWord="Cone"> </Keyword>
		</KeyinTable>
	</SubKeyinTables>

	<KeyinHandlers>
		<KeyinHandler Keyin="csAddins4 CreateElement LineAndLineString1"
			      Function="csAddins4.CreateElement.LineAndLineString1"/>
		<KeyinHandler Keyin="csAddins4 CreateElement LineAndLineString2"
			      Function="csAddins4.CreateElement.LineAndLineString2"/>
		<KeyinHandler Keyin="csAddins4 CreateElement LineAndLineString3"
			      Function="csAddins4.CreateElement.LineAndLineString3"/>
		<KeyinHandler Keyin="csAddins4 CreateElement ShapeAndComplexShape"
			      Function="csAddins4.CreateElement.ShapeAndComplexShape"/>
		<KeyinHandler Keyin="csAddins4 CreateElement TextString"
			      Function="csAddins4.CreateElement.TextString"/>
		<KeyinHandler Keyin="csAddins4 CreateElement Cell"
			      Function="csAddins4.CreateElement.Cell"/>
		<KeyinHandler Keyin="csAddins4 CreateElement Dimension"
			      Function="csAddins4.CreateElement.Dimension"/>
		<KeyinHandler Keyin="csAddins4 CreateElement BsplineCurve"
			      Function="csAddins4.CreateElement.BsplineCurve"/>
		<KeyinHandler Keyin="csAddins4 CreateElement Cone"
			      Function="csAddins4.CreateElement.Cone"/>
</KeyinHandlers>
</KeyinTree>

 

  1. On commands.xml file, set Build Action to Embeded Resource

 

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 csAddins4.dll file.

 

 

  1. Create a new C# Class

Copy and paste the following lines into the CreateElement.cs file.

using Bentley.DgnPlatformNET;
using Bentley.DgnPlatformNET.Elements;
using Bentley.GeometryNET;
using Bentley.MstnPlatformNET;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BIM = Bentley.Interop.MicroStationDGN;

namespace csAddins4
{
    class CreateElement
    {
        static double UorPerMas = Session.Instance.GetActiveDgnModel().GetModelInfo().UorPerMaster;

        public static void LineAndLineString1(string unparsed)
        {
            BIM.Application app = Bentley.MstnPlatformNET.InteropServices.Utilities.ComApp;
            BIM.Point3d ptStart = app.Point3dZero();
            BIM.Point3d ptEnd = ptStart;
            ptStart.X = 10;
            BIM.LineElement lineEle = app.CreateLineElement2(null, ref ptStart, ref ptEnd);
            lineEle.Color = 0; lineEle.LineWeight = 2;
            app.ActiveModelReference.AddElement(lineEle);
        }

        public static void LineAndLineString2(string unparsed)
        {
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            ModelInfo modelInfo = dgnModel.GetModelInfo();
            DSegment3d seg = new DSegment3d(0 * UorPerMas, 5 * UorPerMas, 0 * UorPerMas,
                                            10 * UorPerMas, 10 * UorPerMas, 0 * UorPerMas);
            LineElement lineEle = new LineElement(dgnModel, null, seg);
            lineEle.AddToModel();
            DPoint3d[] ptArr = new DPoint3d[5];
            ptArr[0] = new DPoint3d(15 * UorPerMas, 10 * UorPerMas, 0 * UorPerMas);
            ptArr[1] = new DPoint3d(16 * UorPerMas, 12 * UorPerMas, 0 * UorPerMas);
            ptArr[2] = new DPoint3d(18 * UorPerMas, 8 * UorPerMas, 0 * UorPerMas);
            ptArr[3] = new DPoint3d(20 * UorPerMas, 12 * UorPerMas, 0 * UorPerMas);
            ptArr[4] = new DPoint3d(21 * UorPerMas, 10 * UorPerMas, 0 * UorPerMas);
            LineStringElement lineStrEle = new LineStringElement(dgnModel, null, ptArr);
            lineStrEle.AddToModel();
        }

        public static void LineAndLineString3(string unparsed)
        {
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            ModelInfo modelInfo = dgnModel.GetModelInfo();
            DPoint3d[] ptArr = new DPoint3d[5];
            ptArr[0] = new DPoint3d(0 * UorPerMas, 10 * UorPerMas, 0 * UorPerMas);
            ptArr[1] = new DPoint3d(1 * UorPerMas, 12 * UorPerMas, 0 * UorPerMas);
            ptArr[2] = new DPoint3d(3 * UorPerMas, 8 * UorPerMas, 0 * UorPerMas);
            ptArr[3] = new DPoint3d(5 * UorPerMas, 12 * UorPerMas, 0 * UorPerMas);
            ptArr[4] = new DPoint3d(6 * UorPerMas, 10 * UorPerMas, 0 * UorPerMas);
            CurvePrimitive curPri = CurvePrimitive.CreateLineString(ptArr);
            Element ele = DraftingElementSchema.ToElement(dgnModel, curPri, null);
            ele.AddToModel();
        }

        public static void ShapeAndComplexShape(string unparsed)
        {
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            DPoint3d[] ptArr = new DPoint3d[6];
            ptArr[0] = new DPoint3d(0 * UorPerMas, -6 * UorPerMas, 0 * UorPerMas);
            ptArr[1] = new DPoint3d(0 * UorPerMas, -2 * UorPerMas, 0 * UorPerMas);
            ptArr[2] = new DPoint3d(2 * UorPerMas, -2 * UorPerMas, 0 * UorPerMas);
            ptArr[3] = new DPoint3d(2 * UorPerMas, -4 * UorPerMas, 0 * UorPerMas);
            ptArr[4] = new DPoint3d(4 * UorPerMas, -4 * UorPerMas, 0 * UorPerMas);
            ptArr[5] = new DPoint3d(4 * UorPerMas, -6 * UorPerMas, 0 * UorPerMas);
            ShapeElement shapeEle = new ShapeElement(dgnModel, null, ptArr);
            ElementPropertiesSetter elePropSet = new ElementPropertiesSetter();
            elePropSet.SetColor(0);
            elePropSet.SetWeight(2);
            elePropSet.Apply(shapeEle);
            shapeEle.AddToModel();
            for (int i = 0; i < 6; i++)
                ptArr[i].X += 5 * UorPerMas;
            CurvePrimitive curvePri = CurvePrimitive.CreateLineString(ptArr);
            CurveVector curVec = CurveVector.Create(CurveVector.BoundaryType.Outer);
            curVec.Add(curvePri);
            ptArr[2].Y = -8;
            DEllipse3d ellipse = new DEllipse3d();
            if (DEllipse3d.TryCircularArcFromCenterStartEnd(ptArr[2], ptArr[5], ptArr[0], out ellipse))
            {
                curvePri = CurvePrimitive.CreateArc(ellipse);
                curVec.Add(curvePri);
                Element eleTemp = DraftingElementSchema.ToElement(dgnModel, curVec, null);
                eleTemp.AddToModel();
                elePropSet.SetColor(1);
                elePropSet.SetWeight(2);
                elePropSet.Apply(eleTemp);
                eleTemp.AddToModel();
            }
        }

        public static void TextString(string unparsed)
        {
            DgnFile dgnFile = Session.Instance.GetActiveDgnFile();
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            TextBlockProperties txtBlockProp = new TextBlockProperties(dgnModel);
            txtBlockProp.IsViewIndependent = true;
            ParagraphProperties paraProp = new ParagraphProperties(dgnModel);
            DgnTextStyle txtStyle = DgnTextStyle.GetSettings(dgnFile);
            RunProperties runProp = new RunProperties(txtStyle, dgnModel);
            TextBlock txtBlock = new TextBlock(txtBlockProp, paraProp, runProp, dgnModel);
            txtBlock.AppendText("Hello from C# Addin");
            TextHandlerBase txtHandlerBase = TextHandlerBase.CreateElement(null, txtBlock);
            DTransform3d trans = DTransform3d.Identity;
            trans.Translation = new DVector3d(6 * UorPerMas, 2 * UorPerMas, 3 * UorPerMas);  //UOR unit
            TransformInfo transInfo = new TransformInfo(trans);
            txtHandlerBase.ApplyTransform(transInfo);
            txtHandlerBase.AddToModel();
        }

        public static void Cell(string unparsed)
        {
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            DPoint3d[] ptArr = new DPoint3d[5];
            ptArr[0] = new DPoint3d(-15 * UorPerMas, -5 * UorPerMas, 0 * UorPerMas);
            ptArr[1] = new DPoint3d(-15 * UorPerMas, 5 * UorPerMas, 0 * UorPerMas);
            ptArr[2] = new DPoint3d(-5 * UorPerMas, 5 * UorPerMas, 0 * UorPerMas);
            ptArr[3] = new DPoint3d(-5 * UorPerMas, -5 * UorPerMas, 0 * UorPerMas);
            ptArr[4] = new DPoint3d(-15 * UorPerMas, -5 * UorPerMas, 0 * UorPerMas);
            ShapeElement shapeEle = new ShapeElement(dgnModel, null, ptArr);
            DPlacementZX dPlaceZX = DPlacementZX.Identity;
            dPlaceZX.Origin = new DPoint3d(-10 * UorPerMas, 0, 0);
            DEllipse3d ellipse = new DEllipse3d(dPlaceZX, 5 * UorPerMas, 5 * UorPerMas,
                                            Angle.Zero, Angle.TWOPI);
            EllipseElement elliEle = new EllipseElement(dgnModel, null, ellipse);
            List<Element> listEle = new List<Element>();
            listEle.Add(shapeEle);
            listEle.Add(elliEle);
            DPoint3d ptOri = new DPoint3d();
            DMatrix3d rMatrix = DMatrix3d.Identity;
            DPoint3d ptScale = new DPoint3d(1, 1, 1);
            CellHeaderElement cellHeaderEle = new CellHeaderElement(dgnModel, "CellElementSample", ptOri,
                                            rMatrix, listEle);
            cellHeaderEle.AddToModel();
        }

        public static void Dimension(string unparsed)
        {
            DgnFile dgnFile = Session.Instance.GetActiveDgnFile();
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            double uorPerMast = dgnModel.GetModelInfo().UorPerMaster;
            DimensionStyle dimStyle = new DimensionStyle("DimStyle", dgnFile);
            dimStyle.SetBooleanProp(true, DimStyleProp.Placement_UseStyleAnnotationScale_BOOLINT);
            dimStyle.SetDoubleProp(1, DimStyleProp.Placement_AnnotationScale_DOUBLE);
            dimStyle.SetBooleanProp(true, DimStyleProp.Text_OverrideHeight_BOOLINT);
            dimStyle.SetDistanceProp(0.5 * uorPerMast, DimStyleProp.Text_Height_DISTANCE, dgnModel);
            dimStyle.SetBooleanProp(true, DimStyleProp.Text_OverrideWidth_BOOLINT);
            dimStyle.SetDistanceProp(0.4 * uorPerMast, DimStyleProp.Text_Width_DISTANCE, dgnModel);
            dimStyle.SetBooleanProp(true, DimStyleProp.General_UseMinLeader_BOOLINT);
            dimStyle.SetDoubleProp(0.01, DimStyleProp.Terminator_MinLeader_DOUBLE);
            dimStyle.SetBooleanProp(true, DimStyleProp.Value_AngleMeasure_BOOLINT);
            dimStyle.SetAccuracyProp((byte)AnglePrecision.Use1Place,
                                            DimStyleProp.Value_AnglePrecision_INTEGER);
            int alignInt = (int)DimStyleProp_General_Alignment.True;
            StatusInt status = dimStyle.SetIntegerProp(alignInt, DimStyleProp.General_Alignment_INTEGER);
            int valueOut;
            dimStyle.GetIntegerProp(out valueOut, DimStyleProp.General_Alignment_INTEGER);
            DgnTextStyle textStyle = new DgnTextStyle("TestStyle", dgnFile);
            LevelId lvlId = Settings.GetLevelIdFromName("Default");
            CreateDimensionCallbacks callbacks = new CreateDimensionCallbacks(dimStyle, textStyle,
                                            new Symbology(),
                                            lvlId, null);
            DimensionElement dimEle = new DimensionElement(dgnModel, callbacks, DimensionType.SizeArrow);
            if (dimEle.IsValid)
            {
                DPoint3d pt1 = DPoint3d.Zero, pt2 = DPoint3d.FromXY(uorPerMast * 10, uorPerMast * 0);
                dimEle.InsertPoint(pt1, null, dimStyle, -1);
                dimEle.InsertPoint(pt2, null, dimStyle, -1);
                dimEle.SetHeight(uorPerMast);
                DMatrix3d rMatrix = DMatrix3d.Identity;
                dimEle.SetRotationMatrix(rMatrix);
                dimEle.AddToModel();
            }
        }

        public static void BsplineCurve(string unparsed)
        {
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            DPoint3d[] ptArr = new DPoint3d[5];
            ptArr[0] = new DPoint3d(0 * UorPerMas, 25 * UorPerMas, 0 * UorPerMas);
            ptArr[1] = new DPoint3d(5 * UorPerMas, 35 * UorPerMas, 0 * UorPerMas);
            ptArr[2] = new DPoint3d(10 * UorPerMas, 25 * UorPerMas, 0 * UorPerMas);
            ptArr[3] = new DPoint3d(15 * UorPerMas, 35 * UorPerMas, 0 * UorPerMas);
            ptArr[4] = new DPoint3d(20 * UorPerMas, 25 * UorPerMas, 0 * UorPerMas);
            MSBsplineCurve msBsplineCurve = MSBsplineCurve.CreateFromPoles(ptArr, null, null, 3, false, true);
            CurvePrimitive curvePri = CurvePrimitive.CreateBsplineCurve(msBsplineCurve);
            Element ele = DraftingElementSchema.ToElement(dgnModel, curvePri, null);
            ele.AddToModel();
        }

        public static void Cone(string unparsed)
        {
            DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
            DPoint3d ptTop = new DPoint3d(2 * UorPerMas, -15 * UorPerMas, 0 * UorPerMas);
            DPoint3d ptBottom = new DPoint3d(2 * UorPerMas, -15 * UorPerMas, 3 * UorPerMas);
            DMatrix3d rMatrix = DMatrix3d.Identity;
            ConeElement coneEle = new ConeElement(dgnModel, null, 2 * UorPerMas, 1 * UorPerMas,
                                            ptTop, ptBottom, rMatrix, true);
            coneEle.AddToModel();
        }
    }

    //Used By Dimension
    class CreateDimensionCallbacks : DimensionCreateData
    {
        private DimensionStyle m_dimStyle;
        private DgnTextStyle m_textStyle;
        private Symbology m_symbology;
        private LevelId m_levelId;
        private DirectionFormatter m_directionFormatter;

        public CreateDimensionCallbacks(DimensionStyle dimStyle, DgnTextStyle textStyle, Symbology symb,
                                            LevelId levelId, DirectionFormatter formatter)
        {
            m_dimStyle = dimStyle;
            m_textStyle = textStyle;
            m_symbology = symb;
            m_levelId = levelId;
            m_directionFormatter = formatter;
        }

        public override DimensionStyle GetDimensionStyle()
        {
            return m_dimStyle;
        }

        public override DgnTextStyle GetTextStyle()
        {
            return m_textStyle;
        }

        public override Symbology GetSymbology()
        {
            return m_symbology;
        }

        public override LevelId GetLevelId()
        {
            return m_levelId;
        }

        public override int GetViewNumber()
        {
            return 0;
        }

        public override DMatrix3d GetDimensionRotation()
        {
            return DMatrix3d.Identity;
        }

        public override DMatrix3d GetViewRotation()
        {
            return DMatrix3d.Identity;
        }

        public override DirectionFormatter GetDirectionFormatter()
        {
            return m_directionFormatter;
        }
    }
}

 

  1. Modify MyAddins.cs file

Open the MyAddins.cs file and modify it as follows:

using Bentley.DgnPlatformNET;
using Bentley.MstnPlatformNET;
using System.Windows.Forms;

namespace csAddins4
{
    [AddIn(MdlTaskID = "csAddins4")]
    internal sealed class MyAddin : AddIn
    {
        public static MyAddin Addin = null;

        private MyAddin(System.IntPtr mdlDesc) : base(mdlDesc)
        {
            Addin = this;
        }

        protected override int Run(string[] commandLine)
        {
            return 0;
        }
    }
}

 

 

  1. Build Solution

 

Under menus, select Build > Build Solution to compile the solution.

 

 

  1. Run your Add-in program

 

   Graphical elements are successfully created and added to the active model. Try all other CREATEELEMENT key-ins.

 

You can download the source code for this wiki here.

 

Prev: Run and debug Add-ins Next: Adding Windows Forms to Add-ins