第5章、给Addins添加Windows窗体


本章我们将介绍如何在Mstn中使用模态(Modal)对话框、非模态(Modeless)对话框、工具设置(ToolSettings)对话框和可停靠(Dockable)对话框。模态、非模态和停靠类对话框在各种应用软件中都会用到,你能从互联网上找到许多与之相关的内容。工具设置对话框是Mstn特有的,它本身是一个非模态的,但其内容会随着用户选择工具的不同而自动改变。CE版本Addins中对话框可以利用WinForm和WPF这两种方式实现,两者都可以用VS中的可视化工具直接设计对话框界面,这会比使用传统的MDL的.r资源编程效率高得多。写过大量乏味的.r资源的人会感觉到用Addins编写界面简直是一种享受。下面我们来一步步扩展我们的csAddins应用程序。

1.在VS中打开csAddins解决方案,点选菜单Project > Add Windows Form…,在出现的窗体中Name栏输入ModalForm.cs并点击Add按钮建立一个新的窗体。如下图所示。

2.下面就将展现可视化的威力了,点选Toolbox图标按钮(或当Form1聚焦时按组合键Ctrl+Alt+X)打开工具框窗口,其中有各种窗体控件供我们选择,拖拽一个控件到Form1窗体上,摆好其位置。点选Properties Window图标按钮(或当Form1聚焦时按组合键Alt+Enter)打开属性窗口,其中有大量的属性需要我们来设置。如下图所示:

3.最终设计好的ModalForm如下图所示。需要设置的属性如下。其中将textBox1的Modifiers属性设置为Public,其目的是要在调用该窗体的类DemoForm中访问ModalForm类中的值。注意,属性中Location和Size不需通过手工键入设置,通过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.下面我们来建立一个非模态对话框。在csAddins项目中插入一个WinForm,名称为MultiScaleCopyForm。之所以如此命名是因为该WinForm还会在下一章中被用到。从VS的工具栏中拖拽各种控件到窗体上并设置窗体以及控件的属性如下。

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.为窗体和控件增加事件处理。
①在VS的MultiScaleCopyForm设计窗口(可通过在VS的解决方案浏览器中右击MultiScaleCopyForm.cs并在弹出的菜单中选择View Designer打开)中选中窗体本身,如果还没有打开VS的属性窗框请按步骤2所属打开,在VS的属性窗口中切换到Events页,分别找到FormClosed和Load两个事件,双击它们建立MultiScaleCopyForm_FormClosed和MultiScaleCopyForm_Load事件处理方法;
②再切回到MultiScaleCopyForm设计窗口,选中txtScale控件,在VS的属性窗口的Events页找到KeyPress事件并双击增加txtScale_KeyPress事件处理方法;
③类似地,增加txtXOffset_KeyPress和txtCopies_KeyPress事件处理方法;
④由于txtYOffset和txtZOffset与txtXOffset非常相似,我们可以让这三个控件共享一个事件处理方法txtXOffset_KeyPress,为此,先在设计窗口选中txtYOffset控件,然后在其属性窗口的Events页找到KeyPress事件,此时不要双击而要从下拉框中找到并选择已经存在的txtXOffset_KeyPress即可。对txtZOffset的处理与对txtYOffset的处理完全相同;
⑤ 在设计窗口中双击Load Default按钮增加btnDefault_Click事件处理方法。

6.在VS的解决方案浏览器中右击MultiScaleCopyForm.cs并在弹出的菜单中选择View Code打开MultiScaleCopyForm的源代码,按如下所示修改您的代码。注意如下几个关键点:
①为了操作Windows注册表,需要using Microsoft.Win32;
②为使窗体在Mstn中浮于其他窗体之上,我们需要该窗体派生于Bentley.MstnPlatformNET.WinForms.Adapter类;
③三个KeyPress方法是对TextBox控件输入的内容进行有效性校验;
④当用户点击Load Default按钮时,btnDefault_Click方法将每个TextBox控件中的Tag属性的值复制到了Text属性中;
⑤每当窗体被关闭时执行MultiScaleCopyForm_FormClosed方法,该方法将5个TextBox控件中的当前值写入到了Windows注册表中;当窗体下次被打开时会执行MultiScaleCopyForm_Load方法,该方法从注册表中装载保存的各个TextBox的值。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32;
using Bentley.MstnPlatformNET.WinForms;

namespace csAddins
{
    public partial class MultiScaleCopyForm : //Form
        Adapter
    {
        public MultiScaleCopyForm()
        {
            InitializeComponent();
        }

        //窗体关闭时,将对话框控件中用户输入的数据保存到注册表中
        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());
        }

        //Load对话框时,将保存在注册表中用户最近输入的控件内容恢复到界面控件中
        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 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();
        }
    }
}

警告:我们将窗体的基类从Form改为Adapter后VS将不能可视化地编辑您的WinForm,此时当你切换到设计窗体时会出现如下的错误提示。这就需要您先将Adapter改为Form,可视化编辑,编辑完后再将Form改回为Adapter。

7.类似地,建立NoteCoordForm窗体如下。其中的各控件都采用标准的动作即可,不需要关联事件处理方法。唯一需要注意的是,要将其基类从Form改为Adapter。我们后续会将该窗体作为工具设置框来使用。该窗体中的四个单选按钮的名称分别为rdoHoriz、rdoVert、rdoEN和rdoXY,其中rdoHoriz和rdoEN的Checked属性为True。rdoHoriz和rdoVert在一个组中,rdoEN和rdoXY在另一个组中。

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.准备三个图片文件分别用来代表模态、非模态和工具对话框。然后右击项目名csAddins并在弹出的菜单中选择Properties打开项目属性窗体,切换到资源页,在保证Images类型的资源选中的前提下点击Add Resource来增加图片资源。如下图所示:

9.新建一个窗体ToolbarForm,在其中添加三个按钮控件和一个工具提示(ToolTip)控件。将上一步的三个图片文件分别指定给这三个按钮。最后的结果如下图所示。

Form (Name) = ToolbaForm / FormBorderStyle = FixedToolWindow / MaximizeBox = False / MinimumBox = False
                                               / ShowIcon = False / ShowInTaskbar = False / Size = 141, 83/ 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.分别双击这三个按钮构造出其事件处理方法。然后编辑ToolbarForm.cs源代码如下。注意如下两个关键点:
① ToolbarForm派生于基类Adapter和接口IGuiDockable,因为该窗体要能可停靠(Dockable),所以需要实现IGuiDockable接口下的GetDockedExtent和WindowMoving两个方法;
② 当用户点击按钮时,向Mstn发送一个命令。具体的命令处理函数在下一步来实现。

using Bentley.MstnPlatformNET;
using Bentley.MstnPlatformNET.GUI;
using Bentley.MstnPlatformNET.WinForms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace csAddins
{
    //窗体类要派生于Bentley.MstnPlatformNET.WinForms.Adapter,才能附着与Mstn主程序界面的上层,否则在用户与Mstn主界面交互时,其会被隐藏与主界面之后
    //在用户拖动我们的窗体时,IGuiDockable接口可以用来控制窗体的行为
    public partial class ToolbarForm : //Form
        Adapter, IGuiDockable
    {
        public ToolbarForm()
        {
            InitializeComponent();
        }

        //用户拖动窗体致使窗体停靠至边界时触发此函数的调用,可在此函数中控制是否允许停靠
        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)
        {

            Session.Instance.Keyin("csAddins DemoForm Modal");
        }

        private void btnTopLevel_Click(object sender, EventArgs e)
        {
            Session.Instance.Keyin("csAddins DemoForm TopLevel");
        }

        private void btnToolSettings_Click(object sender, EventArgs e)
        {
            Session.Instance.Keyin("csAddins DemoForm ToolSettings");
        }
    }
}

11.新建一个文件名为DemoForm.cs的DemoForm类用来打开不同类型的对话框,它同时也是命令处理类,其内容如下。针对不同的对话框类型,我们分别调用了Adapter中的AttachAsGuiDockable、AttachAsTopLevelForm、AttachToToolSettings方法。

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

namespace csAddins
{
    
    //调用Bentley.MstnPlatformNET.WinForms.Adapter的不同Attach成员函数,将我们的窗体显示到Mstn的主界面上层
    //函数中IMatchLifetime参数就是我们的入口类的对象实例
    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)
        {
            MultiScaleCopyForm myForm = new MultiScaleCopyForm();
            myForm.AttachAsTopLevelForm(MyAddin.Addin, false);
            myForm.Show();
        }
        public static void ToolSettings(string unparsed)
        {
            NoteCoordForm myForm = new NoteCoordForm();
            myForm.AttachToToolSettings(MyAddin.Addin);
            myForm.Show();
        }
    }
}

12.修改已有的commands.xml文件,增加四个新命令csAddins DemoForm Toolbar|Modal|TopLevel|ToolSettings。需要增加的行在如下代码中用褐色表示出来。

<SubKeyinTables>
    <KeyinTable ID="CreateElement">
      <Keyword SubtableRef="Commands" CommandWord="CreateElement">
        <Options Required="true"/>
      </Keyword>
      <Keyword SubtableRef="DemoForm" CommandWord="DemoForm">
        <Options Required="true"/>
      </Keyword>
    </KeyinTable>
  </SubKeyinTables>

  <SubKeyinTables>
    <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>
    <KeyinTable ID="DemoForm">
      <Keyword CommandWord="Toolbar"/>
      <Keyword CommandWord="Modal"/>
      <Keyword CommandWord="TopLevel"/>
      <Keyword CommandWord="ToolSettings"/>
    </KeyinTable>
  </SubKeyinTables>
  
  
  <KeyinHandlers>
    <KeyinHandler Keyin="csAddins CreateElement LineAndLineString1"
        Function="csAddins.CreateElement.LineAndLineString1"/>
    <KeyinHandler Keyin="csAddins CreateElement LineAndLineString2"
        Function="csAddins.CreateElement.LineAndLineString2"/>
      <KeyinHandler Keyin="csAddins CreateElement LineAndLineString3"
          Function="csAddins.CreateElement.LineAndLineString3"/>
      <KeyinHandler Keyin="csAddins CreateElement ShapeAndComplexShape"
          Function="csAddins.CreateElement.ShapeAndComplexShape"/>
      <KeyinHandler Keyin="csAddins CreateElement TextString"
          Function="csAddins.CreateElement.TextString"/>
      <KeyinHandler Keyin="csAddins CreateElement Cell"
          Function="csAddins.CreateElement.Cell"/>
      <KeyinHandler Keyin="csAddins CreateElement Dimension"
          Function="csAddins.CreateElement.Dimension"/>
      <KeyinHandler Keyin="csAddins CreateElement BsplineCurve"
          Function="csAddins.CreateElement.BsplineCurve"/>
      <KeyinHandler Keyin="csAddins CreateElement Cone"
          Function="csAddins.CreateElement.Cone"/>
    <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>

13.在VS中编译整个项目。如果能一次编译成功那你就太幸运了。当成功生成csAddins.dll后,回到Mstn中装载它,在Keyin框中键入csAddins DemoForm Toolbar即可调出步骤9中所示的工具栏。点击每个按钮能分别打开模态、非模态和工具设置对话框。同时,这个工具栏对话框可停靠。

到目前为止的项目源代码如下,您可以下载后根据您机器的软件安装位置修改项目属性然后就能编译成可执行的csAddins.dll了。