** 第6章 更多的用户界面:添加自定义数据 ** ** **
在本章中,我们将介绍 .NET API 的用户界面部分能做些什么。我们首先将介绍一个自定义上下文菜单(快捷菜单)。接下来我们将实现一个无模式可停靠的面板(一个真正的 AutoCAD 增强辅助窗口)来支持拖放操作。接着我们将介绍通过模式窗体选取实体。最后,我们将介绍使用 AutoCAD 的选项对话框来设置雇员的缺省值。
本章还会介绍和上面内容有关的 API 。
** 第一部分 自定义上下文菜单 ** ** **
到目前为止,我们所写的代码只与 CommandMethod 属性定义的命令行进行相互操作。一个 AutoCAD .NET 程序能通过一个特殊的类来实现装载时的初始化工作。这个类只要实现 IExtensionApplication .NET 接口并暴露一个组件级别的属性(此属性把类定义为 ExtensionApplication ),就可以响应一次性的装载和卸载事件。例子:
[assembly: ExtensionApplication( typeof (Lab6_CS.AsdkClass1))]
public class AsdkClass1 : IExtensionApplication
{
** 1) ** 现在修改 AsdkClass1 类来实现上面的接口。要实现这个接口,你必须实现 Initialize() 和 Terminate () 函数。因为我们要实现的是一个接口,而接口中的函数总是定义为纯虚拟的。
public void Initialize()
{
AddContextMenu();
EmployeeOptions.AddTabDialog();
}
public void Terminate()
{
}
为了加入自定义上下文菜单,我们必须定义一个 ‘ContextMenuExtension’ 类的成员。这个类位于 Autodesk.AutoCAD.Windows 命名空间中。
要使用 ContextMenuExtension ,我们必须使用 new 关键字来进行初始化,给必要的属性赋值,并调用 Application.AddDefaultContextMenuExtension() 。上下文菜单的工作方式是:对于每个菜单条目,我们定义一个成员函数来处理菜单单击事件。我们可能通过 .NET 的代理来实现。我们使用 C# 关键字 += 和 -= 确定让哪个函数来处理事件。请尽快熟悉这种设计模式,因为在 C# 中会使用很多。
** 2) ** 添加一个 ‘ContextMenuExtension’ 成员变量和下面两个用来添加和移除自定义菜单的函数。请好好研究一下代码来看看究竟发生了什么。
void AddContextMenu()
{
try
{
m_ContextMenu = new ContextMenuExtension();
m_ContextMenu.Title = "Acme Employee Menu";
Autodesk.AutoCAD.Windows.MenuItem mi;
mi = new Autodesk.AutoCAD.Windows.MenuItem("Create Employee");
mi.Click += new EventHandler(CallbackOnClick);
m_ContextMenu.MenuItems.Add(mi);
Autodesk.AutoCAD.ApplicationServices.Application.AddDefaultContextMenuExtension(m_ContextMenu);
}
catch
{
}
}
void RemoveContextMenu()
{
try
{
if ( m_ContextMenu != null )
{
Autodesk.AutoCAD.ApplicationServices.Application.RemoveDefaultContextMenuExtension(m_ContextMenu);
m_ContextMenu = null ;
}
}
catch
{
}
}
注意我们在代码中使用了 ‘CallbackOnClick’ 函数。我们希望这个函数(我们现在还没有定义)响应菜单项选择事件。在我们的例子中,我们想要做的是调用我们的成员函数 ‘Create()’ 。请加入下面的代码。
void CallbackOnClick( object Sender, EventArgs e)
{
Create();
}
现在,从 Initialize() 中调用 AddContextMenu() 函数,同样地,请在 Terminate() 中调用 RemoveContextMenu() 。
_ 请运行代码。使用 NETLOAD 来装载编译好的组件,然后在 AutoCAD 的空白处右击……你应该可以看到 ’Acme ‘快捷菜单了。如果失败了,明明你做的都是正确的……为什么呢? _
通常, AutoCAD 的数据是存储在文档中的,而访问实体的命令有权修改文档。当我们运行代码来响应上下文菜单单击事件,我们是从命令结构的外部来访问文档。当我们调用的代码尝试通过添加一个雇员来修改文档时,我们就碰到了错误。正确的做法是必须锁住文档,这可以通过使用 Document.LockDocument() 命令来实现。
** 3) ** 修改 CallbackOnClick 来锁住文档:
void CallbackOnClick( object Sender, EventArgs e)
{
DocumentLock docLock = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.LockDocument();
Create();
docLock.Dispose();
}
注意,我们保留了一个 ‘DocumentLock’ 对象的拷贝。要把文档解锁,我们只要销毁这个 DocumentLock 对象。
_ 再次运行代码。现在应该可以看到快捷菜单了。 _ _ _
** 第2部分 无模式对话框、可进行拖放的可停靠面板 ** ** **
为了使我们的用户界面和 AutoCAD 实现无缝链接,我们要尽可能在所有的地方使用同样的用户界面结构。这会使应用程序看起来与 AutoCAD 结合的很好,并有效地减少代码的重复。一个很好的例子是 AutoCAD 中的可停靠面板。
使用 .NET API ,我们可以创建一个简单的窗体并把它放到面板中。我们可以实例化一个自定义的 ‘PaletteSet’ 对象来包含窗体,并可以把这个 PaletteSet 定义成我们喜欢的样式。
** 4) ** ** ** 在解决方案浏览器中通过右击工程来添加一个用户控件。给它命名为 ModelessForm 。使用控件工具箱,加入如下所示的编辑框和标签控件。
使用属性窗口设置三个编辑框的属性。设置如下:
< 首先是最上面的编辑框 >
( Name ) = tb_Name
Text = < 请输入一个名字 >
< 第二个编辑框 >
(Name) = tb_Division
Text = Sales
< 第三个编辑框 >
(Name) = tb_Salary
Text = < 请输入薪水 >
要使用 .NET API 实例化一个面板对象,你必须要实例化用户控件对象(无模式窗体)和 ‘PaletteSet’ 对象。调用 PaletteSet 的成员函数 Add 来传递用户控件对象。
** 5) ** 接下来,我们要加入一个命令来创建面板。在类中加入一个名为 CreatePalette 的函数和 CommandMethod 属性来定义名为 “PALETTE” 的命令。
请看一下下面的代码块。这是实例化面板的代码。
ps = new Autodesk.AutoCAD.Windows.PaletteSet("Test Palette Set");
ps.MinimumSize = new System.Drawing.Size(300, 300);
System.Windows.Forms.UserControl myCtrl = new ModelessForm();
ps.Add("test", myCtrl);
ps.Visible = true ;
** 6) ** 把上面的代码加入到 CreatePalette() 函数。 ‘ps’ 需要在函数的外部声明:
private Autodesk.AutoCAD.Windows.PaletteSet ps;
在函数的实例化面板代码之前加入检查 ps 是否为 null 的代码。
编译并运行工程。在 AutoCAD 中装载组件,运行 _ ‘PALETTE’ _ _ 命令来检查面板是否被装载。 _
使用 PaletteSet.Style 来看看 PaletteSetStyles 对象。例如:
ps.Style = PaletteSetStyles.ShowTabForSingle;
我们也可以试试诸如透明性的属性,例如:
ps.Opacity = 90;
注意:要使用 PaletteSet 和 PaletteSetStyles 对象,你必须加入两个命名空间 Autodesk.AutoCAD.Windows 和 Autodesk.AutoCAD.Windows.Palette
在我们继续之前,让我们执行一个快速的维护更新:请在 AsdkClass1 类中加入下列成员:
public static string sDivisionDefault = "Sales";
public static string sDivisionManager = "Fiona Q. Farnsby";
这些值将被用作为部门和部门经理的缺省值。由于它们被声明为 ’static’ ,它们在每个程序中只实例化一次,并在组件装载的时候实例化。
** 第 ** ** 2a ** ** 部分 在无模式窗体中加入拖放支持 ** ** **
在这部分,我们将加入允许我们使用面板窗体中编辑框的值来创建一个雇员。当用户从面板中拖动到 AutoCAD 中,将会提示输入职位,一个新的雇员实体将使用这些值来进行创建。
** 7) ** 为了支持拖放,我们首先需要一个对象来进行拖动。在编辑框的下面,另外加入一个名为 Label4 的标签控件,设置标签的文本为一些提示性的东西( ‘Drag to Create Employee’ )。通过这个标签,我们可以在 AutoCAD 中处理拖放。
要捕捉到什么时候拖动事件发生,我们必须要知道什么时候鼠标开始操作。
首先,我们要在类的构造函数中注册事件,代码如下:
Label4.MouseMove += new System.Windows.Forms.MouseEventHandler(Label4_MouseMove);
** 8) ** ** ** 在 ModelessForm 类中加入下面的函数声明:
private void Label4_MouseMove( object sender, System.Windows.Forms.MouseEventArgs e)
{
if (System.Windows.Forms.Control.MouseButtons == System.Windows.Forms.MouseButtons.Left)
{
// start dragDrop operation, MyDropTarget will be called when the cursor enters the AutoCAD view area.
Autodesk.AutoCAD.ApplicationServices.Application.DoDragDrop( this , this , System.Windows.Forms.DragDropEffects.All, new MyDropTarget());
}
}
通常事件处理器有 2 个输入参数,一个 object 类的 sender 和与事件有关的参数。对于 MouseMove ,我们也要做同样的事情。
_ 运行这个工程,检查一下当鼠标经过文本的时候,函数是否被调用的。 _ _ _
我们还可以进一步知道是不是按了鼠标左键 :
if (System.Windows.Forms.Control.MouseButtons == System.Windows.Forms.MouseButtons.Left)
{
}
我们需要一个方法来检测什么时候对象被拖入到 AutoCAD 。我们可以使用 .NET 的基类 DropTarget 来实现。要使用它,你只要创建从这个基类派生的类并实现你想要的函数。在我们这个例子中,我们需要的是 OnDrop() 。
** 9) ** 在工程中加入一个从 Autodesk.AutoCAD.Windows.DropTarget 派生的类 ‘MyDropTarget’ 。如果你把这个类加入到 ModelessForm .cs 文件中,请把这个类加入到 ModelessForm 类之后。
override public void OnDrop(System.Windows.Forms.DragEventArgs e)
{
}
在这个函数中,我们最后会调用 AsdkClass1 的成员 CreateDivision() 和 CreateEmployee ,传入 ModelessForm 类中的编辑框的值。要实现这个功能,我们需要一个方法来连接 ModelessForm 实例。最佳的方法是通过 DragEventArgs 。但首先我们要把鼠标事件连接到 MyDropTarget 类。
** 10) ** 加入下面的代码到鼠标左键( MouseButtons.Left )处理函数中:
Autodesk.AutoCAD.ApplicationServices.Application.DoDragDrop( this , this , System.Windows.Forms.DragDropEffects.All, new MyDropTarget());
注意我们传入 ’this’ 两次。第一次是用于 Control 参数,第二次是用于传入用户自定义数据。因为我们传入的是 ModelessForm 类的实例,所以我们可以在放下的时候使用它来获取编辑框的值。
** 11) ** 回到 OnDrop 处理函数,让我们使用参数来调用创建雇员的函数。首先,添加职位提示的代码。在 AsdkClass1.Create() 中已经有相关的代码了,位于 ‘Get Employees Coordinates…’. 注释下面。添加此代码来提示输入职位。
** 12) ** 接下来,获取传入到 DragEventArgs 参数的 ModelessForm 对象:
ModelessForm ctrl = (ModelessForm)e.Data.GetData( typeof (ModelessForm));
请注意一下怎样通过 typeof 关键字把参数强制转化为 ModelessForm 的实例。
** 13) ** 使用上面的实例来调用 AsdkClass1 成员:
AsdkClass1.CreateDivision(ctrl.tb_Division.Text, AsdkClass1.sDivisionManager);
AsdkClass1.CreateEmployee(ctrl.tb_Name.Text, ctrl.tb_Division.Text, Convert.ToDouble(ctrl.tb_Salary.Text), prPosRes.Value);
注意: AsdkClass1 的方法要不通过 AsdkClass1 的实例来调用,那么方法必须被声明为 ’ public static ’ 。因为 public static 方法只能调用其它的 public static 方法,你需要修改几个 AsdkClass1 类中的方法为 ’ public static ’ 。请你进行相关的修改(应该至少有 4 项要修改)。
** 14) ** 最后,因为我们处理的事件位于 AutoCAD 命令之外,我们必须再次在会修改数据库的代码处锁住文档。请加入锁住文档的代码,加入的方法与前面的上下文菜单是一样的。
_ 编译、装载并运行组件,使用 _ _ PALETTE _ _ 命令。你应该可以使用拖放操作来创建一个雇员了。 _ _ _
_ _
** 第三部分 从有模式窗体中选择实体 ** ** **
本章的以下部分将演示获取一个用户在屏幕上选择的雇员实例的详细信息,并把信息显示在一个有模式窗体的编辑框中。这部分的重点是创建一个有模式窗体,并在执行选择操作而窗体要失去焦点时隐藏它。为了获取