第6章、用DgnPrimitiveTool和DgnElementSetTool实现交互式命令


您肯定使用过Mstn中的放置直线和复制元素命令吧?其实Mstn中的命令分为视图命令和基本命令两大类,基本命令又分为放置类和修改类。这一章我们要实现的两个命令就分别属于放置类和修改类。它们分别需要用派生于DgnPrimitiveTool和DgnElementSetTool的类来实现。第一个要实现的是动态绘制指定点的坐标,第二个要实现的是对指定元素执行多次缩放复制。这两个命令都具有一定的实用价值,是用户在实际工作中提出来的。为了简化这一章的内容,我们将在上一章的基础上做尽量少的修改,不再增加新的命令了。原来的命令名可能看起来不是特别合适,不过好在我们是让用户通过点选图标来启动命令,命令名的不恰当体现得不是太明显。如果您有兴趣,可以增加两个新的更贴切的命令,编写新的命令处理函数(这里介绍的DgnPrimitiveTool和DgnElementSetTool用法相对来说是比较复杂的,本章只是粗略演示了一下其简单用法,读者有兴趣或者有工作上的需要想要详细学习其用法的话,可以参考另外一篇专门讲解交互式工具开发的博文:学习Microstation交互式工具开发)。下面就让我们开始一步步操作来看看最终的执行结果吧。

1.在MyAddins.cs中修改Run方法内容如下。这样能保证我们一装载csAddins.dll程序集就能出现如上一章第9步所示的工具栏。

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

namespace csAddins
{
    [AddIn(MdlTaskID = "csAddins")]
    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("csAddins DemoForm Toolbar");
            return 0;
        }
    }
}

2.如下图所示以代码方式打开NoteCoordForm.Desinger.cs文件,翻到代码尾部,将几个控件的声明由private改成public。修改后的代码段如下。之所以要进行这种修改是因为我们要在随后的其他类代码中访问这些public成员变量。这些成员变量的值反映了用户界面的操作结果。

        

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文件如下:

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.为了实现动态标注指定点坐标的功能,我们新添加一个派生于DgnPrimitiveTool的新类,该类的名字叫做NoteCoordClass,类代码如下。有关该类的帮助文档目前还没有公布出来,不过因为CE版本下Addin的框架于NativeCode的框架几乎是平行的,在NativeCode中有一个同名的类,所以这个类的使用说明我们可以参照NativeCode的帮助文档(…\Bentley\MicroStationCONNECTSDK\doc\MicroStationAPI.chm)中对此类的解释。

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;

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

        //OnPostInstall函数是在我们的工具类真正激活成为当前工具以后被调用的一个函数,在此函数中我们将工具类对应的界面Attach到ToolSetting窗口上
        //每一个交互式工具都会将自己用来供用户输入参数的界面Attach到ToolSetting窗口上,Attach后会覆盖掉先前工具的界面
        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;
        }

        //工具激活以后,当鼠标在视图中点击左键时会触发OnDataButton函数的执行,通过函数参数“ev”可以获取到鼠标点击的坐标点
        protected override bool OnDataButton(DgnButtonEvent ev)
        {
            if(0==m_nPoints)
            {
                //BeginDynamics执行以后,当鼠标在视图中移动时会不停且高频率的调用OnDynamicFrame函数,即启动动态
                BeginDynamics();
                m_Point = ev.Point;
                m_nPoints = 1;
                return false;
            }
            Element element = CreateNoteElement(ev);
            element.AddToModel();
            //OnReinitialize函数内部会调用到OnRestartTool函数
            OnReinitialize();
            return true;
        }

        //启动动态(执行BeginDynamics函数)后,随着鼠标在视图中的移动,OnDynamicFrame函数会被不停且高频率地被调用,通过函数参数“ev”可以获取到鼠标当前所处地坐标点
        //我们在此函数中动态绘制元素来展示绘制效果
        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);
        }

        //OnCleanup函数在工具卸载(工具中调用ExitTool,或者启动其他交互式工具)时被调用,我们在此函数中将工具类界面从ToolSetting窗口中移除
        protected override void OnCleanup()
        {
            m_myForm.DetachFromMicroStation();
        }
    
        //工具激活以后,当鼠标在视图中点击右键时会触发OnResetButton函数的执行
        protected override bool OnResetButton(DgnButtonEvent ev)
        {
            OnRestartTool();
            return true;
        }
        
        protected override void OnRestartTool()
        {
            InstallNewTool();
        }
    }
}
 

我们在方法OnPostInstall中通过调用AttachToToolSettings和Show将myForm显示在了工具设置框中,在命令OnCleanup中通过调用DetachFromMicroStation将myForm卸载。OnPostInstall是在调用InstallTool后且工具类真正成为当前命令后才被调用的。在DgnPrimitiveTool的基类DgnTool中还有一个OnInstall方法,此方法是调用InstallTool后,工具类真正成为当前命令前被调用的。这样我们可以在该函数中做一些判断,如果不符合特定条件的话,返回false,将不会启动当前命令。

5.下面实现多次缩放复制功能。为此需要增加一个派生于DgnElementSetTool的类如下。和DgnPrimitiveTool类似,这个类的说明帮助需要到NativeCode的帮助文档里边找。

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

namespace csAddins
{
    //DgnElementSetTool在DgnPrimitiveTool地基础上封装了元素拾取的功能,如果我们的交互式工具操作过程中需要选择元素来完成工作的话,工具类需要派生于DgnElementSetTool
    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();
        }
        
        //用户选择完元素后如果立即执行修改操作时,则NeedAcceptPoint需要返回false,如果需要用户再额外点击以确认执行修改操作时,NeedAcceptPoint应返回true
        protected override bool NeedAcceptPoint()
        {
            return false;
        }

        //用户选中的元素会被传递给OnElementModify函数,在此函数中完成对元素的修改,如果此函数返回Success则后台会将修改的结果写入文件,否则不做任何操作
        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;
        }
    }
}

6.最后,让我们修改DemoForm类中的TopLevel和ToolSettings方法如下。TopLevel用于启动多重缩放复制,ToolSettings用于启动标注指定点坐标。他们的后台都调用了从DgnTool继承来的InstallTool方法。需要说明的是,为简单起见,标注指定点坐标程序仅限于二维模型中工作,同时未考虑文字注释比例问题。修改后的DemoForm.cs文件如下所示。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

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

8.生成您的csAddins并进行测试。在一个二维模型中事先绘制一个正方形,装载csAddins后点击工具栏中的第三个图标,选择某个角点后会拖出一个动态的标注线和指定点坐标文字,再点击一点确定标注位置。生成的两个标注如下图所示:

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

其实,Mstn提供有不需要编程定制界面的功能。这些定制的界面会被保存到一个叫做.DGNLIB的文件中,只要该DGNLIB文件位于Mstn的配置变量MS_DGNLIBLIST或MS_GUIDGNLIBLIST指到的某个目录下,则Mstn启动时就会加载该DGNLIB文件。通过打开该DGNLIB并在Ribbon界面上选择右键菜单Customize Bibbon可以实现这种定制。由于本系列博客不是讨论Mstn的定制的,所以这里仅仅是提及而已。