Autodesk 官方最新的 .NET 教程(一) (C# )

1   Hello World: 访问 ObjectARX .NET 封装类
      
在这一章中,我们将使用 Visual Studio .NET 来创建一个新的类库工程。通过这个工程,你可以创建一个能被 AutoCAD 装载的 .NET  dll 文件。这个 dll 文件会向 AutoCAD 加入一个名为“ HelloWorld ”的新命令。当用户运行这个命令后,在 AutoCAD 命令行上将显示“ Hello World ”文本。
 
1)
启动 Visual Studio.NET ,选择”文件 > 新建 > 工程” (File> New> Project) 。在新建工程对话框中选择工程类型为” Visual C# 工程”,然后选择”类库”模板,在工程名字框中输入” Lab1 ”,然后选择工程存放的位置。点击确定按钮来创建工程。
2)
在工程的 Class1.cs 文件中,一个公有类“ Class1 ”已经被系统自动创建了。接下来向这个类加入命令。要加入命令,你必须使用 AutoCAD .NET 托管封装类。这些托管封装类包含在两个托管模块中。要加入对这两个托管模块的引用,请用鼠标右键单击”引用”然后选择”添加引用”。在弹出的”添加引用”对话框中选择”浏览”。在”选择组件”对话框中,选择 AutoCAD 2006 的安装目录(这里假定为 C:\Program Files\AutoCAD 2006\ ),在这个目录下找到“ acdbmgd.dll ”然后选择并打开它。再一次选择”浏览”,在 AutoCAD 2006 的安装目录下找到“ acmgd.dll ”并打开它。当这两个组件被加入后,请单击”添加引用” 对话框中的”确定”按钮。正如它们的名字所表示的, acdbmgd.dll 包含 ObjectDBX 托管类,而 acmgd.dll 包含 AutoCAD 托管类。
3)
使用对象浏览器( Visual Studio.NET 的”查看 > 其它窗口 > 对象浏览器”菜单项)来浏览加入的两个托管模块所提供的类。请展开“ AutoCAD .NET Managed Wrapper ”对象(在对象浏览器中显示为 acmgd ),在整个教程中我们将使用这个对象中的类。在本章中,我们将使用 Autodesk.AutoCAD.EditorInput.Editor ”类的一个实例来在 AutoCAD 命令行中显示文本。请再展开“ ObjectDBX .NET Managed Wrapper 对象(在对象浏览器中显示为 acdbmgd ),这个对象中的类将被用来访问和编辑 AutoCAD 图形中的实体(这部分内容将在以后的章节中介绍)。
4)        
引用了 ObjectARX .NET 封装类后,我们就可以导入它们。在 Class1 类的声明语句(位于 Class1.cs 文件的顶部的)之前,导入 ApplicationServices, EditorInput Runtime 命名空间。
 
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
 
5)        
接下来在类 Class1 中加入命令。要加入能在 AutoCAD 中调用的命令,你必须使用“ CommandMethod ”属性。这个属性由 Runtime 命名空间提供。在类 Class1 中加入下列属性和函数。
 
[CommandMethod("HelloWorld")]
    public void HelloWorld()
{
 
}
 
6)        
当“ HelloWorld ”命令在 AutoCAD 中运行的时候,上面定义的 HelloWorld 函数就会被调用。在这个函数中,一个 Editor 类的实例将被创建。 Editor 类拥有访问 AutoCAD 命令行的相关方法,它还包括选择对象和其它一些重要的功能。 AutoCAD 当前活动文档的 Editor 对象可以使用 Application 类来访问。当 Editor 对象被创建后,你可以使用它的 WriteMessage 方法在命令行中显示“ Hello World ”文本。在 HelloWorld 函数中加入以下代码:
 
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("Hello World");
 
7)        
要在 AutoCAD 中调试这个程序,你可以让 Visual Studio.NET 启动一个 AutoCAD 进程。在解决方案管理器中右键单击“ Lab1 ”,然后选择”属性”。在 Lab1 的属性页对话框中,选择” 配置属性 > 调试”。在”启动”项中,选择”调试模式”为”程序”,在”启动程序”的右边单击省略号按钮然后选择 AutoCAD 2006 安装目录下的 acad.exe 。设置好以后,按 F5 来启动一个 AutoCAD 进程。这样就会编译你的程序然后自动启动 AutoCAD ,而当编译后有错误的时候就会停止。请修正你可能碰到的任何错误。
8)        
NETLOAD ”命令被用来装载托管程序。在 AutoCAD 命令行中输入 NETLOAD ,会出现”选择 .NET 组件”的对话框。选择上面生成的“ lab1.dll ”然后打开它。
9)        
在命令行中输入“ HellowWorld ”。如果一切顺利的话,命令行中将显示“ Hello World ”文本。切换到 Visual Studio.NET ,在 ed.WriteMessage( Hello World ); 语句处加入一个断点。在 AutoCAD 中再次运行 HelloWorld 命令,你会注意到你可以跟踪代码的运行。 Visul Studio.NET 的”调试”菜单有好几项可以用来跟踪程序的运行。
      
如果有时间的话,请浏览一下 CommandMethod 属性。你会发现它有七种不同的形式。在上面的例子中,我们使用了最简单的形式,它只有一个输入参数(命令的名字)。你可以使用其它的形式来控制命令的工作方式,例如你可以确定命令组的名字、全局和局部名字、命令标识(命令如何来运行)等。

Autodesk 官方最新的 .NET 教程(二) (C# )

2   .NET AutoCAD 向导及 Editor
       
在第一章中,我们使用的是类库模板,这样就不得不手工加入 acdbmdg. dll acmgd.dll 这两个引用。在这一章中,我们将使用 AutoCAD 托管 C# 应用程序向导来创建 .NET 工程,它会自动加入以上两个引用。在开始本章之前,你首先得安装 ObjectARX 向导 (ObjectARX2006 开发包的 \utils\ObjARXWiz\ArxWizards.msi)
 
1)   
启动 Visual Studio .NET ,选择”文件 > 新建 > 工程” (File> New> Project) 。在新建工程对话框中选择工程类型为” Visual C# 工程”,然后选择“ AutoCAD Managed CS Project Application ”模板。在工程名字框中输入” Lab2 ”,然后选择工程存放的位置。点击确定按钮,“ AutoCAD Managed CSharp Application Wizard ”对话框将会出现。因为我们不需要使用非托管代码,所以不要选择“ Enable Unmanaged Debugging ”项。“ Registered Developer Symbol ”将会使用你在安装 ObjectARX 向导时输入的值。单击” finish ”按钮来创建工程。
2)   
下面来看一下向导生成的工程。在解决方案浏览器中,你会看到 acdbmgd acmgd 已经被引用了。在 Class.cs 文件中,“ Autodesk.AutoCAD.Runtime ”命名空间已被导入,工程使用“ Registered Developer Symbol ”的名字来命名缺省的公有类。向导还为类加入了一个 CommandMethod 属性和一个函数,它们用于 AutoCAD 命令。
3)   
在前一章中,我们使用一个“ Autodesk.AutoCAD.EditorInput.Editor ”类的实例对象在 AutoCAD 命令行上输出文本。在这一章中,我们将使用这个类来提示用户在 AutoCAD 图形中选择一个点,然后将用户选择的点的 x,y,z 值显示出来。和前一章一样,请导入 Autodesk.AutoCAD.ApplicationServices Autodesk.AutoCAD.EditorInput 命名空间。
4)   
把向导生成的 CommandMethod 属性的值改为有意义一些的名字如“ selectPoint ”(函数的名字可以不用修改)。 PromptPointOptions 类用来设置提示字符串和其它的一些控制提示的选项。这个类的一个实例作为参数被传入到 Editor.GetPoint 方法。在函数的开始,实例化这个类,设置字符串参数为“ Select a point ”。因为 Editor.GetPoint 方法会返回一个 PromptPointResult 类的实例对象,所以我们也要把它实例化。
 
PromptPointOptions prPointOptions =
new PromptPointOptions("Select a point");
PromptPointResult prPointRes;
 
5)   
接下来实例化一个 Editor 类的对象并使用参数为 PromptPointOptions 对象的 GetPoint 方法。用 GetPoint 方法的返回值来给上面声明的 PromptPointResult 对象赋值。赋值好以后,我们可以测试 PromptPointResult 对象的状态,如果不是 OK 就返回。
 
prPointRes = ed.GetPoint(prPointOptions);
       if (prPointRes.Status != PromptStatus.OK)
       {
           ed.WriteMessage("Error");
}
 
6)   
如果 PromptPointResult 对象返回了一个有效的点,我们就可以使用 WriteMessage 方法把结果输出到命令行。 PromptPointResult.Value ToString 方法使输出非常容易:
 
ed.WriteMessage("You selected point "
 prPointRes.Value.ToString)
 
7)   
F5 来运行一个调试 AutoCAD 的进程。(注意:向导已经设置好用 acad.exe 来调试)在 AutoCAD 命令行中输入 NETLOAD ,选择 Lab2.dll 并打开。在命令行中输入你起的命令名字( selectPoint )。在选择点的提示下,单击图形中的任一点。如果一切正常的话,你可以在命令行中看到你所选的点的坐标值。在 Class.cs 文件的“ ed.WriteMessage("Error"); ”行加入断点,然后再次运行 selectPoint 命令。这一次,在选择点的提示下按 ESC 键而不是选择一个点。 PromptPointResult 对象的状态就不是 OK 了,所以上面代码中的 if 语句就会被执行,“ ed.WriteMessage("Error") ; 语句就会被调用。
8)   
接下来我们将加入另外一个命令,它可以获取两个点之间的距离。向导没有添加命令的功能,所以我们必须手工添加。在 Class.cs 文件的选择点的函数( getPoint )下面添加一个名为 getDistance 的新命令。加入命令的方法请参考上一章的内容或本章的源代码,这里就不列出了。使用 CommandMethod 属性并使字符串参数为“ getdistance ”或其它类似的名字。在命令的函数中使用 PromptDistanceOptions 代替 PromptPointOptions 。当然 GetDistance 方法的返回值是一个 PromptDoubleResult 类的实例对象,所以请用 PromptDoubleResult 来代替 PromptPointResult
 
PromptDistanceOptions prDistOptions = new    
     PromptDistanceOptions("Find distance, select first point:");
          PromptDoubleResult prDistRes;
prDistRes = ed.GetDistance(prDistOptions);
 
9)      
和前面的命令一样,也可以测试 PromptDoubleResult 的状态,然后用 WriteMessage 方法在命令行中显示值。
 
if (prDistRes.Status != PromptStatus.OK)
         {
             ed.WriteMessage("Error");
         }
      else
         {
ed.WriteMessage("The distance is: " + prDistRes.Value.ToString());
   
  }

Autodesk 官方最新的 .NET 教程(三)( c#

3   数据库 基础 创建我们自己的 Employee 对象

打开 Lab3 文件夹下的 Lab3 工程文件,或或接着 Lab2 的代码。

在这一章中,我们将创建一个‘ Employee 对象’(包括一个圆,一个椭圆和一个多行文本对象),这个对象属于一个自定义的 EmployeeBlock ’块(这个块驻留在‘ EmployeeLayer ’层,当在模型空间插入这个块的时候,‘ EmployeeLayer ’层就会拥有这个块的一个块索引)。本章的每一个步骤中的代码都可以运行,这样做的目的可以使你更清楚地知道每一部分代码完成的功能。第一步将简要说明一下如何在模型空间创建一个圆。

 

 

这一章的重点是在 AutoCAD 中访问数据库的基础。主要内容包括事务处理( Transaction )、对象 Id ObjectId )、符号表( symbol tables ,如块表 BlockTable 和层表 LayerTable )以及对象引用。使用的其它一些对象如颜色 Color 、三维点 Point3d 和三维向量 Vector3d ,都和各自的步骤有关,但重点应该放在数据库基础上。

1)    创建一个名为‘ CREATE ’的命令,它调用函数 CreateEmployee() 。这个函数用来在模型空间 (MODELSPACE) 的( 10 10 0 )点处创建一个半径为 2.0 的圆:

[CommandMethod("test")]

public void createCircle()

{

 

// 首先声明我们要使用的对象

Circle circle; // 这个是我们要加入到模型空间的圆

BlockTableRecord btr;// 要加入圆,我们必须打开模型空间

BlockTable bt; // 要打开模型空间,我们必须通过块表 (BlockTable) 来访问它

 

// 我们使用一个名为‘ Transaction ’的对象,把函数中有关数据库的操作封装起来

Transaction trans;

 

// 使用 TransactionManager StartTransaction() 成员来开始事务处理

trans = HostApplicationServices.WorkingDatabase.TransactionManager.StartTransaction();

 

// 现在创建圆……请仔细看这些参数——注意创建 Point3d 对象的‘ New ’和 Vector3d 的静态成员 ZAxis

circle = new Circle(new Point3d(10, 10, 0), Vector3d.ZAxis, 2);

bt = (BlockTable)trans.GetObject(HostApplicationServices.WorkingDatabase.BlockTableId, OpenMode.ForRead);

 

// 使用当前的空间 Id 来获取块表记录——注意我们是打开它用来写入

btr = (BlockTableRecord)trans.GetObject(HostApplicationServices.WorkingDatabase.CurrentSpaceId,OpenMode.ForWrite );

 

// 现在使用 btr 对象来加入圆

btr.AppendEntity(circle);

trans.AddNewlyCreatedDBObject(circle, true); // 并确定事务处理知道要加入圆!

 

// 一旦完成以上操作,我们就提交事务处理,这样以上所做的改变就被保存了……

trans.Commit();

 

// …然后销毁事务处理,因为我们已经完成了相关的操作(事务处理不是数据库驻留对象,可以销毁)

trans.Dispose();

 

}

 

 

请仔细阅读一下上面的代码块的结构,可以通过注释来了解相关的细节。

注意:要编译代码,你必须导入 Autodesk.AutoCAD.DatabaseServices Autodesk.AutoCAD.Geometry 命名空间

运行这个函数来看看它是否可行。应该会在图形中创建一个在 (10,10,0) 处的半径为 2.0 的白色的圆。

 

2)    我们可以减少代码的输入量,这可以通过声明一个 Database 变量代替 HostApplicationServices.WorkingDatabase 来实现:

           Database db = HostApplicationServices.WorkingDatabase;

 

使用这个变量来代替在代码中出现的 HostApplicationServices.WorkingDatabase

 

3)    在上面的代码中,我们没有使用任何异常处理,而异常处理对一个正确的 .NET 应用程序来说是非常重要的。我们要养成使用异常处理的好习惯,所以让我们在这个函数中加入 try-catch-finally

4)    为了使代码紧凑,我们可以把许多变量的声明和初始化放在同一个语句中。现在,你的代码看起来应该是这样的:

 

 [CommandMethod("CREATE")]

public void CREATEEMPLOYEE()

{

 

Database db = HostApplicationServices.WorkingDatabase;

Transaction trans = db.TransactionManager.StartTransaction();

    try

   {

Circle circle = new Circle(new Point3d(10, 10, 0), Vector3d.ZAxis, 2);

BlockTable bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead);

BlockTableRecord btr = (BlockTableRecord)trans.GetObject(HostApplicationServices.WorkingDatabase.CurrentSpaceId,OpenMode.ForWrite);

btr.AppendEntity(circle);

trans.AddNewlyCreatedDBObject(circle, true);

trans.Commit();

  }

 

 

 catch

  {

ed.WriteMessage("Error ");

 }

finally

{

trans.Dispose();

}

}

End Function

 

运行你的代码来进行测试……

上面的 catch 块只显示一个错误信息。实际的清理工作是在 finally 块中进行的。这样做的理由是如果在事务处理被提交( Commit() )之前, Dispose() 被调用的话,事务处理会被 销毁。我们认为如果在 trans.Commit() 之前出现任何错误的话,你应该销毁事务处理(因为 Commit 将永远不会被调用)。如果在 Dispose() 之前调用了 Commit() ,也就是说没有任何错误发生,那么事务处理将会被提交给数据库。

所以基于上面的分析, Catch 块其实并不是必须的,因为它只用来通知用户程序出现了一个错误。它将在下面的代码中被去掉。

5)    现在让我们在 Employee 加入剩下的部分:椭圆和多行文本的实例。

       多行文本实体:

              中心点应该与圆心的创建一样:

                     (建议:创建一个名为‘ center ’而值为 10,10,0 Point3d 变量来表示中心点)

              多行文本的内容可以是你的名字。

       椭圆(提示:你可以先看一下 Ellipse 的构造函数)

              法向量应该沿着 Z 轴(请查看 Vector3d 类型)

              主轴设为 Vector3d(3,0,0) (提示:不要忘了用 new

              半径比例设为 0.5

              椭圆还必须闭合(也就是说,开始和结束点必须相同)

运行你的代码来进行测试……应该可以生成一个圆、一个椭圆和一个中心点在 10,10,0 的多行文本。

注意:和事务处理对象有关的 .NET API 中的 Try-Catch-Finally 块结构,应该是异常观察者。实际上我们是在 try 块中实例化对象的,但没有显式地销毁它们。当产生异常的时候可能会产生问题,特别是当观察者注意到我们实际上用的是封装的非托管对象!记住,当资源不再使用的时候,垃圾收集机制就会回收内存。垃圾收集机制会不时的调用封装类的 Dispose() 方法,删除非托管对象。

这里还要注意的是 Dispose() 作用于封装的非托管类对象的方式取决于对象是否是数据库驻留对象。由非数据库驻留对象调用的 Dispose() 会删除非托管对象,而由数据库驻留对象调用的 Dispose() 只是关闭它们。
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

6)         接下来让我们来创建一个新的函数,它用来新建一个颜色为黄色,名字为“ EmployeeLayer AutoCAD 层。

这个函数应该检查是否这个层已经存在,但不管这个层是否存在,函数都应该返回“ EmployeeLayer ”的 ObjectId 。下面是这个函数的代码:

public ObjectId CreateLayer()

{

ObjectId layerId; // 它返回函数的值

Database db = HostApplicationServices.WorkingDatabase;

Transaction trans = db.TransactionManager.StartTransaction();

// 首先取得层表……

LayerTable lt = (LayerTable)trans.GetObject(db.LayerTableId, OpenMode.ForWrite);

// 检查 EmployeeLayer 层是否存在……

if (lt.Has("EmployeeLayer"))

{

         layerId = lt["EmployeeLayer"];

}

else

{

// 如果 EmployeeLayer 层不存在,就创建它

LayerTableRecord ltr = new LayerTableRecord();

ltr.Name = "EmployeeLayer"; // 设置层的名字

ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2);

layerId = lt.Add(ltr);

trans.AddNewlyCreatedDBObject(ltr, true);

}

 

trans.Commit();

trans.Dispose();

return layerId;

}

 

是不是觉得这个函数的基本结构与在模型空间加入实体的代码比较类似?访问数据库的方法都是这样的:使用事务处理来获取数据库对象,在符号表(模型空间所在的块表也是符号表之一)中加入实体,然后让事务处理知道。

7)    在这个函数中加入异常处理,就像在 CreateEmployee 函数中的一样。

8)    接下来,改变新建层的颜色。下面是实现的代码片断,请把它加入到你的代码中:

ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2)

 

注意: ColorMethod.ByAci 可以让我们使用 AutoCAD ACI 颜色索引……这里为 2 (表示黄色)。

<!--[if !supportLists]-->9)                   <!--[endif]--> 回到 CreateEmployee() 函数,加入把上面创建的几个实体设置到 EmployeeLayer 层的代码。声明一个类型为 ObjectId 的变量,用 CreateLayer 函数的返回值给它赋值。使用每个实体(文本、圆和椭圆)的 LayerId 属性设置它们所在的层。

 

例如: text.LayerId = empId

 

运行代码来查看“ EmployeeLayer ”层是否已被创建,所有已创建的实体是否都在这一层上(应该显示为黄色)

10)   现在为各个实体设置不同的颜色,可以使用 ColorIndex 属性( ColorIndex 属性表示 AutoCAD 的颜色)

       圆为红色- 1

       椭圆为绿色- 3

       文本为黄色- 2

 

运行代码,看看实体的颜色是否为设置的值,即使这些实体是在“ EmployeeLayer ”层上。

11)   接下来,我们要在 AutoCAD 数据库中创建一个独立的块,然后把它插入到块表而不是模型空间中。

首先把 CreateEmployee 函数的名字改为 CreateEmployeeDefinition()

加入以下代码来创建一个独立的块:

 

BlockTableRecord newBtr = new BlockTableRecord();

newBtr.Name = "EmployeeBlock";

newBtrId = bt.Add(newBtr);

trans.AddNewlyCreatedDBObject(newBtr, true);

              

 

12)   现在,请稍微改动一下加入实体到模型空间的代码(改为加入块到块表中,记得加入前要打开块表)。

现在运行代码,然后使用 INSERT 命令来检查是否可以正确插入这个块。

 

13)   最后,我们要创建一个位于模型空间的块索引,它表示上面创建的块的一个实例。这一步留给大家练习。

       下面是你要遵循的最基本的步骤:

<!--[if !supportLists]-->A)                  <!--[endif]--> 创建一个名为 CreateEmployee 新的函数

<!--[if !supportLists]-->B)                  <!--[endif]--> 把命令属性“ CREATE ”移动到 CreateEmployee()

<!--[if !supportLists]-->C)                  <!--[endif]--> 修改 CreateEmployeeDefintion() 来返回新创建的块“ EmployeeBlock ”的 ObjectId ,操作的步骤请参考 CreateLayer() 的作法。

<!--[if !supportLists]-->D)                  <!--[endif]--> 你需要修改 CreateEmployeeDefintion() 来查看块表中是否已包含“ EmployeeBlock ”块,如果包含这个块,则返回它的 ObjectId (做法与 CreateLayer() 一样)。

提示:把‘ bt ’的声明语句移动到 try 块的顶部,使用 BlockTable.Has() 方法,把其它的代码移动到 else 语句:

try

                     {

      // 获取 BlockTable 对象

BlockTable bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForWrite);

                            if ((bt.Has("EmployeeBlock")))

                            {

                                   newBtrId =bt["EmployeeBlock"];

                            }

                            else

      {

 

 

<!--[if !supportLists]-->E)                  <!--[endif]--> 在新创建的 CreateEmployee() 函数中创建一个新的 BlockReference 对象,并把它加入到模型空间。提示:我们可以使用 CreateEmployeeDefinition() 中引用模型空间的代码,这些代码在这里不需要了

<!--[if !supportLists]-->F)                  <!--[endif]--> CreateEmployee 中调用 CreateEmployeeDefinition() 函数,使上面生成的 BlockReference 对象的 BlockTableRecord() 指向 CreateEmployeeDefinition() 函数。提示:请参考 BlockReference 的构造函数。

 

附加的问题:

让我们来看一下代码的运行情况,执行命令会生成一个 EmployeeBlock 的块索引,你会看到它被插入到 20,20,0 而不是 10,10,0 。为什么?

如果你知道原因,那么怎样才能使块索引插入到正确的点?

当你用 List 命令查看块索引时,它会告诉你它位于 0 层(或者当命令运行时位于当前层)。为什么?

怎样才能让块索引总是位于 EmployeeLayer 层?

  Autodesk 官方最新的 .NET 教程(四)( C# 版)

4   数据库 基础 2:  添加自定义数据

在这一章中,我们将创建一个新的字典对象,它用来表示我们雇员就职的 Acme 公司‘(呵呵,当然是虚构的一家公司)的部门。这个“部门”字典对象将包含一个表示部门经理的记录。我们还会加入代码到雇员创建过程,这个过程会加入一个索引到雇员工作的部门。

我们要说明的是如何在 DWG 文件中创建自定义数据,包括“每个图形”的自定义数据和“每个实体”的自定义数据。“每个图形”的自定义数据是指只在整个图形中加入一次的数据,它表示对象可以引用的单一类型或特性。“每个实体”的自定义数据是指是为特定的对象或数据库中的实体加入的数据。

在下面的示例中,我们将加入“每个图形”的自定义数据到命名对象字典 ( 简称 NOD) NOD 存在于每一个 DWG 文件中。“每个实体”的自定义数据加入到一个名为“扩展字典”的字典(可选)中,它表示每一个雇员。每一个由 DBObject 派生的对象都拥有存储自定义数据的扩展字典。而在我们的示例中将包含这种自定义数据如名字、薪水和部门。

因此这一章的重点是字典对象和扩展记录( XRecord ),它们是我们用来表示自定义数据的容器。

首先让我们来创建表示公司的条目。在本章的前几个步骤中,我们将创建如下所示的部门层次结构:

  NOD -命名对象字典

     ACME_DIVISION -自定义公司字典

               销售 (Sales) -部门字典

                       部门经理-部门条目

 

请打开 Lab4 文件夹下的 Lab4 工程,或接着 Lab3 的代码。

<!--[if !supportLists]-->1)                   <!--[endif]--> 我们首先要做的是定义一个新的函数,它用来在命名对象字典 (NOD) 中创建公司字典对象。为这个函数取名为 CreateDivision(), ,并使用命令属性来定义 CREATEDIVISION 命令。

下面是这个函数的代码,它的形式非常简单,只是用来在 NOD 中创建一个 ACME_DIVISION (用来表示公司)

 [CommandMethod("CreateDivision")]

 public void CreateDivision()

 {

       Database db = HostApplicationServices.WorkingDatabase;

       Transaction trans = db.TransactionManager.StartTransaction();

 

       try

       {

              // 首先,获取 NOD ……

              DBDictionary NOD = (DBDictionary)trans.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForWrite);

              // 定义一个公司级别的字典

              DBDictionary acmeDict;

              try

              {

                     // 如果 ACME_DIVISION 不存在,则转到 catch 块,这里什么也不做

                     acmeDict = (DBDictionary)trans.GetObject(NOD.GetAt("ACME_DIVISION"), OpenMode.ForRead);

              }

              catch

              {

                     // 如果 ACME_DIVISION 不存在,则创建它并把它加入到 NOD 中……

                     acmeDict = new DBDictionary();

                     NOD.SetAt("ACME_DIVISION", acmeDict);

                     trans.AddNewlyCreatedDBObject(acmeDict, true);

              }

              trans.Commit();

       }

       finally

       {

              trans.Dispose();

       }

}

 

请仔细阅读一下上面的代码块的结构,可以通过注释来了解相关的细节。特别要注意的是我们是如何用一个 try-catch 块来处理 ACME_DIVISION 是否存在?如果 ACME_DIVISION 字典不存在, GetObject() 将会抛出异常, catch 块被执行,它会创建一个新的字典。

运行这个函数来看它是否可行。可以使用数据库查看工具来检查字典是否已被加入(建议使用 ARX SDK ArxDbg 工具)

<!--[if !supportLists]-->2)                   <!--[endif]--> 接下来,我们要在 ACME_DIVISION 字典中加入销售 (Sales) 条目。销售 (Sales) 条目同样也是一个字典。由于销售 (Sales) 字典与 ACME_DIVISION 字典的关系如同 ACME_DIVISION 字典与 NOD ,所以代码是类似的。定义下面的代码部分在 ACME_DIVISION 字典中创建一个名为’ Sales ’的字典。

代码提示:

 

                   DBDictionary divDict;

                   try

       {

              divDict = (DBDictionary)trans.GetObject(acmeDict.GetAt("Sales"), OpenMode.ForWrite);

       }

       catch

         

 

运行函数来看‘ Sales ’条目是否已加入到 ACME_DIVISION 字典。

<!--[if !supportLists]-->3)                   <!--[endif]--> 现在我们要在这个字典中加入一个特殊的记录,它可以包含任意的自定义数据。我们要加入的数据类型为扩展记录( XRecord ),它可以包含任何东西,因此我们可以让它包含 ResultBuffer 类的对象(就是有些人可能非常熟悉的‘ resbuf ’)。 ResultBuffer 可以存储不同类型的预定义数据。扩展记录存储任意数目的 ResultBuffer 关系列表,因此可能会很大。下表是可以包含在 ResultBuffer 中一些数据类型(位于 Database 类的 DxfCode 枚举中):

 

Start

0

 

Text

1

 

XRefPath

1

 

ShapeName

2

 

BlockName

2

 

AttributeTag

2

 

SymbolTableName

2

 

MstyleName

2

 

SymTableRecName

2

 

AttributePrompt

3

 

DimStyleName

3

 

LinetypeProse

3

 

TextFontFile

3

 

 

在下面的代码部分,我们将创建只包含一个 ResultBuffer 的扩展记录。这个 ResultBuffer 包含一个单一的的字符串值,它表示’ Sales ’部门的部门经理的名字。我们使用和加入字典一样的方法加入扩展记录。唯一的区别是扩展记录与字典的不同:

             mgrXRec = new Xrecord();

  mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, "Randolph P. Brokwell"));

 

请看一下我们是怎样使用 new 来创建一个新的扩展记录。但我们也使用了 new 来创建一个 ResultBuffer ,传入的参数是一个名为‘ TypedValue ’的对象。‘ TypedValue ’对象和 C++ resbuf 的成员‘ restype ’是类似的。这个对象一般表示一个特定类型的 DXF 值,我们使用它来组装诸如扩展数据或扩展记录之类的通用数据容器。在这里,我们简单地使用 DxfCode.Text 键值和“ Randolph P. Brokwell ”数据值来定义一个 TypedValue ,然后把它作为单一的参数传入 ResultBuffer 构造函数 ( new 来调用 ) 中。

XRecord 的’ Data ’属性实际上正是扩展记录链的第一个 ResultBuffer ,我们使用它来表示扩展记录链是从什么地方开始的。

所以接下来的代码块看起来和前面两个非常相似:

Xrecord mgrXRec;

              try

              {

                     mgrXRec = (Xrecord)trans.GetObject(divDict.GetAt("Department Manager"), OpenMode.ForWrite);

              }

              catch

              {

                     mgrXRec = new Xrecord();

                     mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, "Randolph P. Brokwell"));

                     divDict.SetAt("Department Manager", mgrXRec);

                     trans.AddNewlyCreatedDBObject(mgrXRec, true);

              }

 

运行函数并使用数据库查看工具来确定部门经理已被加入到’ Sales ’字典。

 

4)    我们已经定义了公司字典,现在我们要把每个雇员的数据加入到前一章定义的块索引中。我们要加入的数据是:名字、薪水和雇员所属的部门。要加入这些数据,我们要同前几个步骤一样使用扩展记录。因为我们要加入三个条目,所以我们要使扩展记录可以把这些数据联系在一起。

一般来说,扩展记录只能存在于字典中。而我们要为每个雇员加入这些数据(就是本章开头所讲的“每个图形”的自定义数据和“每个实体”的自定义数据),那应该怎么做呢?答案就是:每一个对象或 AutoCAD 中的实体实际上都有一个名为’扩展字典( Extension Dictionary )’的可选字典。我们可以把扩展记录直接加入到这个字典中。

请回到我们在上一章创建的 CreateEmployee() 函数。这个函数是我们创建块索引的地方。

让我们像前面的步骤一样来创建一个新的扩展记录。因为我们要加入 3 个条目,因此我们既可以使用 ResultBuffer Add 方法(它会在扩展记录链中加入一个链接),也可以利用 ResultBuffer 的构造函数(它的一种构造函数可以输入可变数量的参数)。

无论用哪一种方法,请在 CreateEmployee() 函数中使用 ResultBuffer 来创建一个新的 XRecord ResultBuffer 包括以下的类型和值:

       Text Earnest Shackleton         ( 或是你选择的其它雇员的名字 )

       Real 72000                或者更多的薪水 J

       Text Sales                雇员所在的部门

      

5)    要把上面的扩展记录加入到块索引,我们必须把它加入到扩展字典。通常这个字典是不存在的,除非它被明确地创建,块索引就是这种情况。要给一个对象创建扩展字典,你要调用它的成员‘ CreateExtensionDictionary() ’。这个函数不返回任何值,所以要访问它创建的扩展字典,你还得使用对象的‘ ExtensionDictionary ’属性。你可以使用类似于以下的代码来创建并访问扩展字典:

 

       br.CreateExtensionDictionary();

       DBDictionary brExtDict  = (DBDictionary)trans.GetObject(br.ExtensionDictionary, OpenMode.ForWrite, false);

 

由于扩展字典也是字典,我们可以和第 3 步一样在扩展字典中加入扩展记录。请完成有关的代码来创建和访问块索引的扩展字典,加入你在第 4 步中创建的扩展记录,然后把扩展记录加入到事务处理。

6)    返回到 NOD ……因为在 NOD 中创建公司字典只需要一次(就像创建 Employee 块一样),因此我们应该把 CreateDivision 函数的命令属性去掉,而在 CreateEmployeeDefinition() 中调用这个函数。请自己完成这些改变。当所有这些都做完后,当 CREATE 命令第一次运行的时候,所有的函数都会被调用。

7)    下面的步骤和上面的无关。我们将创建一个函数来遍历模型空间,以用来查找加入的 Employee 对象(这里其实是块索引)的数目。在 VB.NET C# 中,我们可以把模型空间块表记录( ModelSpace BlockTableRecord )当作一个集合,这样就可以使用 For Each(C# foreach) 来遍历它。请仔细研究一下下面的代码片断:

 

VB.NET:

            Dim id As ObjectId   首先,定义一个 For 循环要使用的 ObjectId 变量。

            For Each id In btr

                Dim ent As Entity = trans.GetObject(id, OpenMode.ForRead, False) ' 打开当前的对象 !

 

C#:

       foreach (ObjectId id in btr)

       {

            Entity ent = (Entity)trans.GetObject(id, OpenMode.ForRead, false);  // 打开当前的对象 !

 

一旦我们获得模型空间对象,你们就可以定义一个 ObjectId 变量,然后把它用于 For Each 循环 (C# foreach)

现在,我们需要使用一些方法来筛选雇员。我们知道模型空间中的对象都是实体,但不全是雇员。我们需要使用一些方法来加以区分。在这里,我们可以使用 VB.NET TypeOf 关键字并用 CType 进行类型转换( C# GetType 函数和 typeof ):

VB.NET:

                If TypeOf ent Is BlockReference Then

              Dim br As BlockReference = CType(ent, BlockReference)

             

 

C#:

       If(ent.GetType() == typeof(BlockReference))

              BlockReference br = (BlockReference)ent;

 

上面讲的概念对于 AutoCAD 编程是很重要的,因为容器对象经常包含不同类型的对象。你会在 AutoCAD 程序的开发中经常碰到这种类型转化。

请定义一个名为 EmployeeCount() 的函数,函数的结构如上所示,它用来统计模型空间中的块索引的数目。这个函数不会输出任何东西,但你可以使用逐步调试程序来查看整数变量的增加(每发现一个块索引对象)。

8)  接下来,为了把结果输出到命令行,我们需要使用 Application.DocumentManager.MdiActiveDocument.Editor 对象的服务。要使用它,请加入下面的代码:

 

Imports Autodesk.AutoCAD.EditorInput

Imports Autodesk.AutoCAD.ApplicationServices

 

在函数的内部:

Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;


最后,在循环的后面确定找到了多少个块索引:

ed.WriteMessage("Employees Found: " + nEmployeeCount.ToString());

 

第四章结束

下面的代码片断演示了怎样获取 Employee 对象的所有内容,包括 ACME_DIVISION 字典中的部门经理的名字。这部分要在后面的章节中使用,但因为它和本章有关,因此我们把它放在本章作介绍。如果有时间的话,请阅读一下其中的代码来看看它是怎么使用的。它可以被直接放到你的类中并可以运行。命令的名字是 PRINTOUTEMPLOYEE ListEmployee() 函数接收一个 ObjectId 参数,它通过一个 ref 类型的字符串数组返回值(包含相应的雇员数据)。调用它的 PrintoutEmployee() 函数只是用来在命令行中输出这些数据。

我们需要一个遍历并显示所有雇员数据的命令。

public static void ListEmployee(ObjectId employeeId, ref string[] saEmployeeList)

{

       int nEmployeeDataCount = 0;

       Database db = HostApplicationServices.WorkingDatabase;

       Transaction trans = db.TransactionManager.StartTransaction(); // 开始事务处理。

       try

       {

              Entity ent = (Entity)trans.GetObject(employeeId, OpenMode.ForRead, false); // 打开当前对象 !

              if (ent.GetType() == typeof(BlockReference))

              {

// 不是所有的块索引都有雇员数据,所以我们要处理错误

                     bool bHasOurDict = true;

                     Xrecord EmployeeXRec = null;

                     try

                     {

                            BlockReference br = (BlockReference)ent;

                            DBDictionary extDict = (DBDictionary)trans.GetObject(br.ExtensionDictionary, OpenMode.ForRead, false);

                            EmployeeXRec = (Xrecord)trans.GetObject(extDict.GetAt("EmployeeData"), OpenMode.ForRead, false);

                     }

                     catch

                     {

bHasOurDict = false; // 出现了错误……字典或扩展记录不能访问

                     }

 

if (bHasOurDict) // 如果获得扩展字典,而又有扩展记录……

                     {

 

                            // 为雇员列表分配内存

                            saEmployeeList = new String[4];

                            // 加入雇员的名字

                            TypedValue resBuf = EmployeeXRec.Data.AsArray()[0];

                            saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);

                            nEmployeeDataCount += 1;

                            // 加入雇员的薪水

                            resBuf = EmployeeXRec.Data.AsArray()[1];

                            saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);

                            nEmployeeDataCount += 1;

                            // 加入雇员所在的部门

                            resBuf = EmployeeXRec.Data.AsArray()[2];

                            string str = (string)resBuf.Value;

                            saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);

                            nEmployeeDataCount += 1;

                            // 现在,让我们从公司字典中获取老板的名字

                            // NOD 中找到 .

                            DBDictionary NOD = (DBDictionary)trans.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForRead, false);

                                          DBDictionary acmeDict = (DBDictionary)trans.GetObject(NOD.GetAt("ACME_DIVISION"), OpenMode.ForRead);

                            // 注意我们直接使用扩展数据 ...

                                          DBDictionary salesDict = (DBDictionary)trans.GetObject(acmeDict.GetAt((string)EmployeeXRec.Data.AsArray()[2].Value), OpenMode.ForRead);

                            Xrecord salesXRec = (Xrecord)trans.GetObject(salesDict.GetAt("Department Manager"), OpenMode.ForRead);

                            // 最后,把雇员的数据输出到命令行

                            resBuf = salesXRec.Data.AsArray()[0];

                            saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);

                            nEmployeeDataCount += 1;

                     }

              }

              trans.Commit();

       }

       finally

       {

              trans.Dispose();

       }

}

 

      

[CommandMethod("PRINTOUTEMPLOYEE")]

public static  void PrintoutEmployee()

{

       Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

       // 声明我们将在下面使用的工具 ...

       Database db = HostApplicationServices.WorkingDatabase;

       Transaction trans = db.TransactionManager.StartTransaction();

       try

       {

              // 首先,获取块表和模型空间块表记录

BlockTable bt = (BlockTable)trans.GetObject(HostApplicationServices.WorkingDatabase.BlockTableId, OpenMode.ForRead);

              BlockTableRecord btr = (BlockTableRecord)trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);

              // 现在,我们需要把内容输出到命令行。这里可以有一个对象帮助我们:

                      // 下面的部分,我们将遍历模型空间 :

 

              foreach (ObjectId id in btr)

              {

                     Entity ent = (Entity)trans.GetObject(id, OpenMode.ForRead, false); // 打开当前对象 !

                     if (ent is BlockReference)

                     {

                            string[] saEmployeeList = null;// 这是正确的 ... 定义新的列表。

                           

                            ListEmployee(id, ref saEmployeeList);

                            if ((saEmployeeList.Length == 4))

                            {

                                   ed.WriteMessage("Employee Name: {0}", saEmployeeList[0]);

                                   ed.WriteMessage("Employee Salary: {0}", saEmployeeList[1]);

                                   ed.WriteMessage("Employee Division: {0}", saEmployeeList[2]);

                                   ed.WriteMessage("Division Manager: {0}", saEmployeeList[3]);

                            }

                     }

              }

       }

       finally

       {

       }     

}

Autodesk 官方最新的 .NET 教程(五) (C# )

5 用户互操作:提示和选择
 
背景
 
提示通常包含一个描述性信息,伴随一个停止以让用户理解所给的信息并输入数据。数据可以通过多种方式被输入,如通过命令行、对话框或 AutoCAD 编辑窗口。给出的提示要遵循一定的格式,格式要与一般的 AutoCAD 提示相一致,这一点是非常重要的。例如,关键字要用“ / ”号分隔并放在方括号“ [] ”中,缺省值要放在“ <> ”内。对于一个 AutoCAD 用户来说,坚持统一的格式将会减少信息理解错误的产生。
 
当用户在 AutoCAD 命令行中选择一个实体时,实体是使用选择机制被选择的。这种机制包括一个提示,用来让用户知道选择什么并怎样选择(如,窗口或单一实体),然后是一个停顿。
 
试一下诸如 PINE 这种命令来看一下提示的显示, PEDIT 来看一下使用单一实体或多线来进行选择。
 
练习
  Prompts:
 
提示:
 
在本章中,我们将提示输入雇员名字、职位、薪水和部门来创建一个雇员块索引对象。如果输入的部门不存在,我们将提示输入部门经理的名字来创建一个新的部门。在我们继续之前,让我们试着重用以前的代码。
 
为了进行选择,我们将提示用户在一个窗口中进行选择或选择一个实体,而我们只显示选择集中的雇员对象。
 
在前面的章节中,我们创建了一个名叫“ Earnest Shackleton ”的雇员,名字被存储为“ EmployeeBlock ”块定义(块表记录)中的 MText 。如果我们多次插入这个块,那么我们看到的都是同一个雇员的名字。我们怎样才能自定义这个块以使每次插入这个块的时候显示不同雇员的名字?这就要使用块属性的功能了。属性是存储在每一个块索引实例中的文本,并被作为实例的一部分来被显示。属性从存储在块表记录中的属性定义中继承相关的属性。
 
属性:
 
让我们来把 MText 实体类型改变为属性定义。在 CreateEmployeeDefinition() 函数中,把下面的代码替换
  
  //
文本 :
  MText text = new MText();
  text.Contents = "Earnest Shackleton";
  text.Location = center;
  
 

  
  //
属性定义
  AttributeDefinition text = new AttributeDefinition(center, "NoName", "Name:", "Enter Name", db.Textstyle);
  text.ColorIndex = 2;
  
 
试着使用 TEST 命令来测试一下 CreateEmployeeDefinition() 函数:
   [CommandMethod("Test")]
   public void Test()
   {
   CreateEmployeeDefinition();
   }
  
 
你现在应该可以使用 INSERT 命令来插入 EmployeeBlock 块并对每一个实例确定一个雇员名。
 
当你插入 Employee 块时,请注意一下块插入的位置。它是正好被放置在所选点还是有些偏移?试试怎样修复它。(提示:检查块定义中的圆心)
 
修改 CreateEmployee () 以重用
  
  1)
让我们来修改 CreateEmployee() 函数,以让它可以接收名字、薪水、部门和职位并返回创建的雇员块索引的 ObjectId 。函数的形式如下(你可以改变参数顺序)
  public ObjectId CreateEmployee(string name, string division, double salary, Point3d pos)
  
  2)
移除上面函数中的 CommandMethod 属性” CREATE ”,这样它就不再是用来创建雇员的命令。
  3)
修改函数的代码,这样就可以正确地设置块索引的名字、职位、部门和薪水和它的扩展字典。
 
· 替换
  
  BlockReference br = new BlockReference(new Point3d(10, 10, 0), CreateEmployeeDefinition());
  
 

  
  BlockReference br = new BlockReference(pos, CreateEmployeeDefinition());
  
 
· 替换
  
   xRec.Data = new ResultBuffer(
   new TypedValue((int)DxfCode.Text, "Earnest Shackleton"),
   new TypedValue((int)DxfCode.Real, 72000),
   new TypedValue((int)DxfCode.Text, "Sales"));
  
 

  
   xRec.Data = new ResultBuffer(
   new TypedValue((int)DxfCode.Text, name),
   new TypedValue((int)DxfCode.Real, salary),
   new TypedValue((int)DxfCode.Text, division));
  
  4)
因为我们把雇员的名字从 MText 替换成块的属性定义,因此我们要创建一个相应的属性索引来显示雇员的名字。属性索引将使用属性定义的属性。
 
· 替换 :
  
   btr.AppendEntity(br); //
加入索引到模型空间
   trans.AddNewlyCreatedDBObject(br, true); //
让事务处理知道
  
 

  
   AttributeReference attRef = new AttributeReference();
   //
遍历雇员块来查找属性定义
   BlockTableRecord empBtr = (BlockTableRecord)trans.GetObject(bt["EmployeeBlock"], OpenMode.ForRead);
   foreach (ObjectId id in empBtr)
   {
   Entity ent = (Entity)trans.GetObject(id, OpenMode.ForRead, false);
   //
打开当前的对象 !
   if (ent is AttributeDefinition)
   {
   //
设置属性为属性索引中的属性定义
   AttributeDefinition attDef = ((AttributeDefinition)(ent));
   attRef.SetPropertiesFrom(attDef);
   attRef.Position = new Point3d(attDef.Position.X + br.Position.X, attDef.Position.Y + br.Position.Y, attDef.Position.Z + br.Position.Z);
   attRef.Height = attDef.Height;
   attRef.Rotation = attDef.Rotation;
   attRef.Tag = attDef.Tag;
   attRef.TextString = name;
   }
   }
   //
把索引加入模型空间
   btr.AppendEntity(br);
   //
把属性索引加入到块索引
   br.AttributeCollection.AppendAttribute(attRef);
   //
让事务处理知道
   trans.AddNewlyCreatedDBObject(attRef, true);
   trans.AddNewlyCreatedDBObject(br, true);
  
  
 
研究一下上面的代码,看看是怎样把属性定义中除显示用的文本字符串外的属性复制到属性索引的。属性被加入到块索引的属性集合中。这就是你怎样来为每一个实例自定义雇员名字。
  5)
不要忘记返回雇员块索引的 ObjectId ,但要在提交事务处理之后才能返回:
  trans.Commit();
  return br.ObjectId;
  
  6)
测试 CreateEmployee
 
加入一个 Test 命令来测试 CreateEmployee
  
   [CommandMethod("Test")]
   public void Test()
   {
   CreateEmployee("Earnest Shackleton", "Sales", 10000, new Point3d(10, 10, 0));
   }
  
  
 
修改 CreateDivision() 以重用:
 
让我们来修改 CreateDivision () 函数,以让它可以接收部门名字、经理名字并返回创建的部门经理扩展记录的 ObjectId 。如果部门经理已经存在,则不改变经理的名字。
  1)
如果你先前在 CreateEmployeeDefinition() 中调用了 CreateDivision() ,请把它注释掉,因为我们在这里不需要创建一个部门
  2)
  改变 CreateDivision() 的形式让它接收部门和经理的名字并返回一个 ObjectId
  public ObjectId CreateDivision(string division, string manager)
  3)
修改上面函数的代码创建部门的名字和经理:
 
· 替换 :
  
  divDict = (DBDictionary)trans.GetObject(acmeDict.GetAt("Sales"), OpenMode.ForWrite);
  
 
:
  
  divDict = (DBDictionary)trans.GetObject(acmeDict.GetAt(division), OpenMode.ForWrite);
  
 
· 替换 :
  
  acmeDict.SetAt("Sales", divDict);
  
 
:
  
  acmeDict.SetAt(division, divDict);
  
 
· 替换 :
  
  mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, "Randolph P. Brokwell"));
  
 
:
  
  mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, manager));
  
  
 
不要忘了返回部门经理这个扩展记录的 ObjectId ,但要在提交事务处理后才返回。
  trans.Commit();
  //
返回部门经理这个扩展记录的 ObjectId
  return mgrXRec.ObjectId;
  
 
现在把在中 CreateEmployeeDefinition 调用的 CreateDivision 函数给注释掉。
  4)
现在通过使用 TEST 命令来测试调用 CreateDivision 函数。使用 ArxDbg 工具来检查条目是否已被加入到“ ACME_DIVISION ”下的命名对象字典。
  CreateDivision("Sales", "Randolph P. Brokwell")
  
 
使用 CREATE 命令来创建雇员:
  
 
我们将加入一个名为 CREATE 的新命令,此命令用来提示输入雇员的详细资料来创建雇员块索引。让我们来看一下这个命令是怎样使用的。
  1)
让我们加入一个名为 CREATE 的新命令,并声明几个常用的变量和一个 try-finally 块。
  
  [CommandMethod("CREATE")]
  public void CreateEmployee()
  {
   Database db = HostApplicationServices.WorkingDatabase;
   Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
   Transaction trans = db.TransactionManager.StartTransaction();
   try
   {
   trans.Commit();
   }
   finally
   {
   trans.Dispose();
   }
  }
  
  
  2)
让我们来为雇员定义可以用作为提示缺省值的常数。注意,布尔值 gotPosition 是用来判断用户是否已输入职位。
  .
雇员名 - 类型 String - 缺省值 Earnest Shackleton
  .
雇员所在部门名    - 类型: String      - 缺省值“ Sales
  .
薪水 - 类型: Double (non-negative and not zero) - 缺省值 10000
  .
职位 - 类型: Point3d - 缺省值 (0,0,0)
  
 
把这些常数加入到 try 语句后面:
  string empName = "Earnest Shackleton";
  string divName = "Sales";
  double salary = new double();
  salary = 10000;
  Point3d position = new Point3d(0, 0, 0);
  bool gotPosition = new bool();
  //
布尔值用来判断用户是否已输入职位
  gotPosition = false;
  
  3)
现在让我们提示用户输入值。我们先使用 PromptXXXOptions 类来初始化要显示的提示字符串。
  //
提示输入每个雇员的详细资料
  PromptStringOptions prName = new PromptStringOptions("Enter Employee Name <" + empName + ">");
  PromptStringOptions prDiv = new PromptStringOptions("Enter Employee Division <" + divName + ">");
  PromptDoubleOptions prSal = new PromptDoubleOptions("Enter Employee Salary <" + salary + ">");
  PromptPointOptions prPos = new PromptPointOptions("Enter Employee Position or");
  
 
注意,提示字符串用尖括号来显示变量的值。这是 AutoCAD 用来提示用户这个值为缺省值。
  4)
当提示用户输入职位时,我们也提供了一个关键字列表选项,如名字、部门和薪水。如果用户想要在选择一个点的时候改变为其它值,他可以选择那个关键字。
 
一个命令提示的例子如下:
  Command: CREATE
  Enter Employee Position or [Name/Division/Salary]:
  
 
要创建一个雇员,用户会选择一个点而其它的值被设置为缺省值。如果用户要改变其它的值,如名字,他可以输入” N ”或全名” Name ”,然后输入名字:
  Command: CREATE
  Enter Employee Position or [Name/Division/Salary]:N
  Enter Employee Name <Earnest Shackleton>:
  
 
如果用户想要再次选择缺省的名字,他可以按回车键。
 
让我们创建用于职位提示的关键字列表:
  
  //
加入用于职位提示的关键字
  prPos.Keywords.Add("Name");
  prPos.Keywords.Add("Division");
  prPos.Keywords.Add("Salary");
  //
设置提示的限制条件
  prPos.AllowNone = false; //
不允许没有值
  
  
  5)
现在让我们声明 PromptXXXResult 变量来获取提示的结果:
  //prompt results
  PromptResult prNameRes;
  PromptResult prDivRes;
  PromptDoubleResult prSalRes;
  PromptPointResult prPosRes;
  
  6)
直到用户成功输入一个点后,循环才结束。如果输入错误的话,我们会提示用户并退出函数:
 
判断用户是否输入了关键字,我们通过检查 promptresult 的状态来进行:
  //
循环用来获取雇员的详细资料。当职位被输入后,循环终止。
  while (!gotPosition)
  {
   //
提示输入职位
   prPosRes = ed.GetPoint(prPos);
   //
取得一个点
   if (prPosRes.Status == PromptStatus.OK)
   {
   gotPosition = true;
   position = prPosRes.Value;
   }
   else if (prPosRes.Status == PromptStatus.Keyword) //
获取一个关键字
   {
   //
输入了 Name 关键字
   if (prPosRes.StringResult == "Name")
   {
   //
获取雇员名字
   prName.AllowSpaces = true;
   prNameRes = ed.GetString(prName);
   if (prNameRes.Status != PromptStatus.OK)
   {
   return;
   }
   //
如果获取雇员名字成功
   if (prNameRes.StringResult != "")
   {
   empName = prNameRes.StringResult;
   }
   }
   }
   else
   {
   //
获取职位时发生错误
   ed.WriteMessage("***Error in getting a point, exiting!!***" + "\r\n");
   return;
   } //
如果获取一个点
  
  }
  
  7)
上面的代码只提示输入名字,请加入提示输入薪水和部门的代码。
  8)
完成提示输入后,我们将使用获得的值来创建雇员。
  //
创建雇员
  CreateEmployee(empName, divName, salary, position);
   //www.knowsky.com
  9)
现在来检查部门经理是否已存在。我们通过检查 NOD 中部门的扩展记录中的经理名字来进行。如果检查到的是一个空字符串,那么我们会提示用户输入经理的名字。注意,通过修改 CreateDivision() 函数,获取经理的名字变得简单了。
  string manager = "";
  //
创建部门
  //
给经理传入一个空字符串来检查它是否已存在
  Xrecord depMgrXRec;
  ObjectId xRecId;
  xRecId = CreateDivision(divName, manager);
  //
打开部门经理扩展记录
  depMgrXRec = (Xrecord)trans.GetObject(xRecId, OpenMode.ForRead);
  TypedValue[] typedVal = depMgrXRec.Data.AsArray();
  foreach (TypedValue val in typedVal)
  {
   string str;
   str = (string)val.Value;
   if (str == "")
   {
   //
经理没有被设置,现在设置它
   //
先提示输入经理的名字
   ed.WriteMessage("\r\n");
   PromptStringOptions prManagerName = new PromptStringOptions("No manager set for the division! Enter Manager Name");
   prManagerName.AllowSpaces = true;
   PromptResult prManagerNameRes = ed.GetString(prManagerName);
   if (prManagerNameRes.Status != PromptStatus.OK)
   {
   return;
   }
   //
设置经理的名字
   depMgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, prManagerNameRes.StringResult));
   }
  }
  
  
  
  10)
测试 CREATE 命令
  
 
选择集:
 
现在让我们来创建一个命令,当用户在图形中选择一个雇员对象时,它会显示雇员的详细资料。
 
我们会使用上一章中创建的 ListEmployee() 函数在命令行中输出雇员的详细资料。
 
下面是你必须遵循的步骤:
 
调用“ LISTEMPLOYEES ”命令
 
调用 Editor GetSelection() 函数来选择实体
   PromptSelectionResult res = ed.GetSelection(Opts, filter);
  
 
上面的 filter 用来过滤选择集中的块索引。你可以创建如下的过滤列表:
  TypedValue[] filList = new TypedValue[1];
  filList[0] = new TypedValue((int)DxfCode.Start, "INSERT");
  SelectionFilter filter = new SelectionFilter(filList);
  
 
从选择集中获取 ObjectId 数组:
  
   //
如果选择失败则什么也不做
   if (res.Status != PromptStatus.OK)
   return;
   Autodesk.AutoCAD.EditorInput.SelectionSet SS = res.Value;
   ObjectId[] idArray;
   idArray = SS.GetObjectIds();
  
  5.
最后,把选择集中的每个 ObjectId 输入到 ListEmployee() 函数来获取一个雇员详细资料的字符串数组。把雇员的详细资料输出到命令行。例如:
  
  //
获取 saEmployeeList 数组中的所有雇员
  foreach (ObjectId employeeId in idArray)
  {
   ListEmployee(employeeId, ref saEmployeeList);
   //
把雇员的详细资料输出到命令行
   foreach (string employeeDetail in saEmployeeList)
   {
   ed.WriteMessage(employeeDetail);
   }
  
   ed.WriteMessage("----------------------" + "\r\n");
  }