MicroStation commands fall into two main categories: View commands and Basic commands. Basic commands are further classified into Placement and Modification commands. This chapter implements one command from each category by deriving classes from DgnPrimitiveTool and DgnElementSetTool.
The first command dynamically displays the coordinates of a selected point, while the second creates multiple scaled copies of a selected element. Both address practical requirements raised by users. To keep the chapter focused, we will build on the previous chapter with minimal changes and reuse the existing commands. Although their names are not ideal, users launch them from toolbar icons, so this is unlikely to cause confusion. If preferred, you can create two more descriptive commands and add corresponding handlers.
This chapter provides only a basic introduction to DgnPrimitiveTool and DgnElementSetTool. For more detailed guidance, see Learn MicroStation interactive tool development.
Let's work through the steps and review the results.
Update the Run method as shown below. This loads the toolbar as soon as the csAddins6.dll assembly loads.
using Bentley.DgnPlatformNET;
using Bentley.MstnPlatformNET;
using System.Windows.Forms;
namespace csAddins6
{
[AddIn(MdlTaskID = "csAddins6")]
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)
{
Session.Instance.Keyin("csAddins6 DemoForm Toolbar");
return 0;
}
}
}
Open NoteCoordForm.Desinger.cs in the code editor, scroll to the end, and change the required control declarations from private to public, as shown below. This allows other classes to access values that reflect the user's interface choices.
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;
public System.Windows.Forms.Label label1;
public System.Windows.Forms.TextBox txtScale;
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;
To display point coordinates dynamically, add a class named NoteCoordClass that derives from DgnPrimitiveTool. The class code is shown below.
For additional details on DgnPrimitiveTool, refer to the help “…\Bentley\MicroStation2026SDK\Documentation\index.html”, documented in MicroStationAPI native-code section.
using Bentley.DgnPlatformNET;
using Bentley.DgnPlatformNET.Elements;
using Bentley.GeometryNET;
using Bentley.MstnPlatformNET;
using csAddins6;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace csAddins6
{
class NoteCoordClass : DgnPrimitiveTool
{
private NoteCoordForm m_myForm;
private DPoint3d m_Point = new DPoint3d();
private int m_nPoints = 0;
public NoteCoordClass() : base(0, 0)
{
}
public static void InstallNewTool()
{
NoteCoordClass noteCoordClass = new NoteCoordClass();
noteCoordClass.InstallTool();
}
protected override void OnPostInstall()
{
if (m_myForm == null)
{
m_myForm = new NoteCoordForm();
m_myForm.AttachToToolSettings(MyAddin.Addin);
m_myForm.Show();
}
AccuSnap.SnapEnabled = true;
base.OnPostInstall();
}
private Element CreateNoteElement(DgnButtonEvent ev)
{
if (1 != m_nPoints)
return null;
DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
DgnFile dgnFile = Session.Instance.GetActiveDgnFile();
string[] txtStr = new string[2];
DPoint3d[] txtPts = new DPoint3d[2];
Element[] elems = new Element[3];
txtStr[0] = (m_myForm.rdoEN.Checked ? "E=" : "X=") + m_Point.X.ToString("F2");
txtStr[1] = (m_myForm.rdoEN.Checked ? "N=" : "Y=") + m_Point.Y.ToString("F2");
DgnTextStyle txtStyle = DgnTextStyle.GetSettings(dgnFile);
double width = 0, txtLineSpacing = 0;
txtStyle.GetProperty(TextStyleProperty.Width, out width);
txtStyle.GetProperty(TextStyleProperty.Height, out txtLineSpacing);
double txtLen = width * Math.Max(txtStr[0].Length, txtStr[1].Length);
DPoint3d pt1 = ev.Point, pt2 = new DPoint3d();
if (m_myForm.rdoHoriz.Checked)
{
pt2.X = pt1.X + (m_Point.X > pt1.X ? -txtLen : txtLen) * 1.2;
pt2.Y = pt1.Y;
txtPts[0].X = (pt1.X + pt2.X) / 2;
txtPts[0].Y = pt1.Y + txtLineSpacing / 2;
txtPts[1].X = txtPts[0].X;
txtPts[1].Y = pt1.Y - txtLineSpacing / 2 * 3;
}
else
{
pt2.X = pt1.X;
pt2.Y = pt1.Y + (m_Point.Y > pt1.Y ? -txtLen : txtLen) * 1.2;
txtPts[0].X = pt1.X - txtLineSpacing / 2;
txtPts[0].Y = (pt1.Y + pt2.Y) / 2;
txtPts[1].X = pt1.X + txtLineSpacing / 2 * 3;
txtPts[1].Y = txtPts[0].Y;
}
DPoint3d[] ptArr = new DPoint3d[3];
ptArr[0] = m_Point;
ptArr[1] = pt1;
ptArr[2] = pt2;
LineStringElement lineStrEle = new LineStringElement(dgnModel, null, ptArr);
List<Element> eleList = new List<Element>();
eleList.Add(lineStrEle);
for (int i = 1; i < 3; i++)
{
TextBlockProperties txtBlockProp = new TextBlockProperties(dgnModel);
txtBlockProp.IsViewIndependent = false;
ParagraphProperties paraProp = new ParagraphProperties(dgnModel);
paraProp.Justification = TextElementJustification.CenterMiddle;
RunProperties runProp = new RunProperties(txtStyle, dgnModel);
TextBlock txtBlock = new TextBlock(txtBlockProp, paraProp, runProp, dgnModel);
txtBlock.AppendText(txtStr[i - 1]);
TextHandlerBase txtHandlerBase = TextHandlerBase.CreateElement(null, txtBlock);
DTransform3d trans = DTransform3d.Identity;
DPoint3d offset = new DPoint3d();
if (m_myForm.rdoVert.Checked)
{
DRange3d range3d = new DRange3d();
txtHandlerBase.CalcElementRange(out range3d);
trans.Matrix = DMatrix3d.FromColumns(new DVector3d(0, 1, 0),
new DVector3d(-1, 0, 0), new DVector3d(0, 0, 1));
offset = new DPoint3d((range3d.Low.Y - range3d.High.Y) / 2 + txtPts[i - 1].X,
(range3d.Low.X - range3d.High.X) / 2 + txtPts[i - 1].Y, 0);
}
else
{
DRange3d range3d = new DRange3d();
txtHandlerBase.CalcElementRange(out range3d);
offset = new DPoint3d((range3d.Low.X - range3d.High.X) / 2 + txtPts[i - 1].X,
(range3d.High.Y - range3d.Low.Y) / 2 + txtPts[i - 1].Y, 0);
}
trans.Translation = offset;
TransformInfo transInfo = new TransformInfo(trans);
txtHandlerBase.ApplyTransform(transInfo);
eleList.Add(txtHandlerBase);
//if (myForm.rdoVert.Checked)
// elems[i].RotateAboutZ(txtPts[i - 1], Math.PI / 2);
}
DMatrix3d rMatrix = DMatrix3d.Identity;
DPoint3d ptScale = new DPoint3d(1, 1, 1);
CellHeaderElement cellHeaderEle = new CellHeaderElement(dgnModel,
"NoteCoordCell", m_Point, rMatrix, eleList);
return cellHeaderEle;
}
protected override bool OnDataButton(DgnButtonEvent ev)
{
if (0 == m_nPoints)
{
BeginDynamics();
m_Point = ev.Point;
m_nPoints = 1;
return false;
}
Element element = CreateNoteElement(ev);
element.AddToModel();
OnReinitialize();
return true;
}
protected override void OnDynamicFrame(DgnButtonEvent ev)
{
Element element = CreateNoteElement(ev);
if (null == element)
return;
RedrawElems redrawElems = new RedrawElems();
redrawElems.SetDynamicsViewsFromActiveViewSet(Bentley.MstnPlatformNET.Session.GetActiveViewport());
redrawElems.DrawMode = DgnDrawMode.TempDraw;
redrawElems.DrawPurpose = DrawPurpose.Dynamics;
redrawElems.DoRedraw(element);
}
protected override void OnCleanup()
{
m_myForm.DetachFromMicroStation();
}
protected override bool OnResetButton(DgnButtonEvent ev)
{
OnRestartTool();
return true;
}
protected override void OnRestartTool()
{
InstallNewTool();
}
}
}
To create multiple scaled copies, add a class named MultiScaleCopyClass that derives from DgnElementSetTool. The class code is shown below.
For additional details on DgnElementSetTool, refer to the help “…\Bentley\MicroStation2026SDK\Documentation\index.html”, documented in MicroStationAPI native-code section.
using Bentley.DgnPlatformNET;
using Bentley.DgnPlatformNET.Elements;
using Bentley.GeometryNET;
using Bentley.MstnPlatformNET;
using csAddins6;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BIM = Bentley.Interop.MicroStationDGN;
namespace csAddins6
{
class MultiScaleCopyClass : DgnElementSetTool
{
private MultiScaleCopyForm m_myForm;
public MultiScaleCopyClass() : base(0, 0)
{
}
public static void InstallNewTool()
{
MultiScaleCopyClass multiScaleCopyClass = new MultiScaleCopyClass();
multiScaleCopyClass.InstallTool();
}
protected override void OnPostInstall()
{
if (m_myForm == null)
{
m_myForm = new MultiScaleCopyForm();
m_myForm.AttachToToolSettings(MyAddin.Addin);
m_myForm.Show();
}
base.OnPostInstall();
}
protected override void OnCleanup()
{
m_myForm.DetachFromMicroStation();
}
protected override bool OnResetButton(DgnButtonEvent ev)
{
OnRestartTool();
return true;
}
protected override void OnRestartTool()
{
InstallNewTool();
}
protected override bool NeedAcceptPoint()
{
return false;
}
public override StatusInt OnElementModify(Element element)
{
Bentley.Interop.MicroStationDGN.Element newEl;
double dScale = double.Parse(m_myForm.txtScale.Text);
DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
double uorPerMaster = dgnModel.GetModelInfo().UorPerMaster;
DPoint3d offsetPnt = new DPoint3d(double.Parse(m_myForm.txtXOffset.Text) * uorPerMaster,
double.Parse(m_myForm.txtYOffset.Text) * uorPerMaster,
double.Parse(m_myForm.txtZOffset.Text) * uorPerMaster);
BIM.Application app = Bentley.MstnPlatformNET.InteropServices.Utilities.ComApp;
long eleId = element.ElementId;
Bentley.Interop.MicroStationDGN.Element BIMEle = app.ActiveModelReference.GetElementByID
(ref eleId);
for (int i = 0; i < int.Parse(m_myForm.txtCopies.Text); i++)
{
newEl = app.ActiveModelReference.CopyElement(BIMEle);
long longid = newEl.ID;
ElementId elementId = new ElementId(ref longid);
Element newElement = dgnModel.FindElementById(elementId);
DRange3d range = new DRange3d();
((DisplayableElement)newElement).CalcElementRange(out range);
DTransform3d dTransform = DTransform3d.Identity;
dTransform.Translation = new DPoint3d(-(range.Low.X + range.High.X) / 2,
-(range.Low.Y + range.High.Y) / 2,
-(range.Low.Z + range.High.Z) / 2);
DTransform3d dTransform2 = new DTransform3d(new DMatrix3d(dScale, 0, 0, 0, dScale,
0, 0, 0, dScale));
dTransform2 = DTransform3d.Multiply(dTransform2, dTransform);
dTransform = DTransform3d.Identity;
dTransform.Translation = new DPoint3d((range.Low.X + range.High.X) / 2 +
offsetPnt.X, (range.Low.Y + range.High.Y) / 2 + offsetPnt.Y,
(range.Low.Z + range.High.Z) / 2 + offsetPnt.Z);
dTransform2 = DTransform3d.Multiply(dTransform, dTransform2);
newElement.ApplyTransform(new TransformInfo(dTransform2));
newElement.ReplaceInModel(newElement);
eleId = newElement.ElementId;
BIMEle = app.ActiveModelReference.GetElementByID(ref eleId);
}
return StatusInt.Error;
}
}
}
Finally, update the TopLevel and ToolSettings methods in the DemoForm class. TopLevel starts the multiple-scale copy tool, while ToolSettings starts point-coordinate annotation.
Both call; the InstallTool method inherited from DgnTool. For simplicity, the coordinate-annotation tool supports only 2D models and does not account for annotation scale. The updated DemoForm.cs file is shown below.
using csAddins6;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace csAddins6
{
class DemoForm
{
public static void Toolbar(string unparsed)
{
ToolbarForm myForm = new ToolbarForm();
myForm.AttachAsGuiDockable(MyAddin.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)
{
MultiScaleCopyClass.InstallNewTool();
//MultiScaleCopyForm myForm = new MultiScaleCopyForm();
//myForm.AttachAsTopLevelForm(MyAddin.Addin, false);
//myForm.Show();
}
public static void ToolSettings(string unparsed)
{
NoteCoordClass.InstallNewTool();
//NoteCoordForm myForm = new NoteCoordForm();
//myForm.AttachToToolSettings(MyAddin.Addin);
//myForm.Show();
}
}
}
Under menus, select Build > Build Solution to compile the solution.
Build and test csAddins6. First, draw a square in a 2D model. After loading csAddins6, click the third toolbar icon and select a corner of the square. A dynamic leader and coordinate label will appear; click again to place the annotation. The figure below shows two resulting annotations:
Click the second toolbar icon, set the parameters as shown below, select the square, and confirm to create the copy result.
MicroStation can also provide customized interfaces without programming. These interfaces are stored in DGNLIB files. When a DGNLIB is included in MS_DGNLIBLIST or MS_GUIDGNLIBLIST, MicroStation loads it at startup.
To create this customization, open the DGNLIB, right-click the ribbon, and select Customize Ribbon.
You can download the source code for this wiki here.