中国BDN社区-博客 - Learning MicroStation Addins Step by Step[6]
BMPCreated with Sketch.BMPZIPCreated with Sketch.ZIPXLSCreated with Sketch.XLSTXTCreated with Sketch.TXTPPTCreated with Sketch.PPTPNGCreated with Sketch.PNGPDFCreated with Sketch.PDFJPGCreated with Sketch.JPGGIFCreated with Sketch.GIFDOCCreated with Sketch.DOC Error Created with Sketch.
Blog

Learning MicroStation Addins Step by Step[6]

by
YongAn Fu
Expert
Created: (Edited: )

第六章、用IPrimitiveCommandEvents和ILocateCommandEvents实现交互式命令
Chapter 6. Using IPrimitiveCommandEvents and ILocateCommandEvents to implement interactive commands

您肯定使用过Mstn中的放置直线和复制元素命令吧?其实Mstn中的命令分为视图命令和基本命令两大类,基本命令又分为放置类和修改类。这一章我们要实现的两个命令就分别属于放置类和修改类。它们分别需要用派生于IPrimitiveCommandEvents和ILocateCommandEvents接口的类来实现。第一个要实现的是动态绘制指定点的坐标,第二个要实现的是对指定元素执行多次缩放复制。这两个命令都具有一定的实用价值,是用户在实际工作中提出来的。
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. We will implement a placement command and a modification command in this chapter which need classes implement 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 doesn’t 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 let’s start to operate step by step and show off the result of our effort.

1. 在MyAddins.cs开头确保有如下两行using语句,然后修改Run方法内容如下。这样能保证我们一装载csAddins.dll程序集就能出现如上一章第9步所示的工具栏。
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. 如下图所示以代码方式打开NoteCoordForm.Desinger.cs文件,翻到代码尾部,将几个控件的声明由private改成public。修改后的代码段如下。之所以要进行这种修改是因为我们要在随后的其他类代码中访问这些public成员变量。这些成员变量的值反映了用户界面的操作结果。
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.

image

        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. 类似地,修改MultiScaleCopyForm.Designer.cs文件如下:
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. 下面打开DemoForm.cs,我们要对该文件进行大刀阔斧的修改。先确保using语句如下。注意:我们同时使用了using Bentley.Interop.MicroStationDGN;和using BCOM = Bentley.Interop.MicroStationDGN;(using的这两种用法的含义请查询您的C#语言手册)。之所以需要第二个using BCOM=是因为Application和View两个类在System.Windows.Forms和Bentley.Interop.MicroStationDGN两个命名空间下都存在。对于这种情况,我们可以简写成BCOM.Application和BCOM.View以代替Bentley.Interop.MicroStationDGN.Application和Bentley.Interop.MicroStationDGN.View。
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. 为了实现动态标注指定点坐标的功能,我们需要在DemoForm.cs中添加一个实现IPrimitiveCommandEvents接口的新类,该类的名字叫做NoteCoordClass,类代码如下。您可以复制并粘贴这段代码到您的DemoForm.cs中或自己输入。该类必须重写Cleanup、DataPoint、Dynamics、Keyin、Reset和Start六个方法。这些方法的具体含义请参考Mstn VBA帮助文档。这些方法的原型可以通过VS的对象浏览器得到,如下图所示:
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.

image

   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();
        }
    }

我们在命令启动方法Start中通过调用AttachToToolSettings和Show将myForm显示在了工具设置框中,在命令结束方法Cleanup中通过调用DetachFromMicroStation将myForm卸载,从而克服了上一章中一旦将myForm在工具设置框中显示就无法取消的缺点,除非你强行关闭工具设置框。您可能会问到当前命令什么时候结束(即Cleanup方法在什么时候被调用到)?答案是当同级的其他命令启动时。基本命令的级别高于视图命令,而基本命令中的放置类和修改类命令则属于同一级别。基本命令正在执行时插入了一个视图命令,此时基本命令并没有结束而仅仅是挂起,当视图命令结束后按下复位键(默认为鼠标右键)即可恢复基本命令的执行。
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 can’t 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 doesn’t 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. 下面实现多次缩放复制功能。为此需要在DemoForm.cs中增加一个实现ILocateCommandEvents接口的类如下。该类必须重写Accept、Cleanup、Dynamics、LocateFailed、LocateReset和Start六个方法。
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. 最后,让我们修改DemoForm类中的TopLevel和ToolSettings方法如下。TopLevel用于启动多重缩放复制,ToolSettings用于启动标注指定点坐标。他们分别调用了CommandState对象下的StartLocate和StartPrimitive方法。需要说明的是,为简单起见,标注指定点坐标程序仅限于二维模型中工作,同时未考虑文字注释比例问题。
7. Finally, let’s 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. 生成您的csAddins并进行测试。在一个二维模型中事先绘制一个正方形,装载csAddins后点击工具栏中的第三个图标,选择某个角点后会拖出一个动态的标注线和指定点坐标文字,再点击一点确定标注位置。生成的两个标注如下图所示:
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.

image

点击第二个工具图标,如下图所示设置工具框中的参数,选择这个正方形并确认即可生成如下图所示的复制结果。
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.

image image

其实,Mstn提供有不需要编程定制界面的功能。这些定制的界面会被保存到一个叫做.DGNLIB的文件中,只要该DGNLIB文件位于Mstn的配置变量MS_DGNLIBLIST或MS_GUIDGNLIBLIST指到的某个目录下,则Mstn启动时就会加载该DGNLIB文件。通过打开该DGNLIB并选择菜单Workspace > Customize可以实现这种定制。由于本系列博客不是讨论Mstn的定制的,所以这里仅仅是提及而已。下图是定制界面以及定制出的效果。
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 don’t dig into this topic deeply because this blog series aren’t focused on the customization of Mstn. The following pictures are the customization graphic interface and the result of my customization.

image

image

您可以下载如下附件csAddins.dgnlib到MS_DGNLIBLIST指向的某个文件夹下,重启Mstn后就能看到以上定制出的效果了。
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