1.Autodesk官方最新的.net教程(一)(C#版):
修正:
(1)启用对象浏览器方法:
双击托管的类,会自动调用对象浏览器
(2)调试环境设置:
应设2项,否则调试程序无法运行:
启动外部程序为:F:/AutoCAD 2007/acad.exe
工作目录为:F:/AutoCAD 2007/UserDataCache/
2.Autodesk官方最新的.net教程(二)(C#版):
(1)prPointRes = ed.GetPoint(prPointOptions); 前面应加入下列语句:
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
(2).NET AutoCAD 向导生成的项目中调试项目中只加入了外部程序,没有加入工作目录,需手工加入。
(3)加入第2个cad命令的方法:将上一个命令原版拷贝过来,直接修改就可以了。
3.Autodesk官方最新的.net教程(三)(C#版):
原文中部分变量与类型中没有空格。
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)、符号表(symboltables,如块表BlockTable和层表LayerTable)以及对象引用。使用的其它一些对象如颜色Color、三维点Point3d和三维向量Vector3d,都和各自的步骤有关,但重点应该放在数据库基础上。
1) 创建一个名为‘CREATE’的命令,它调用函数CreateEmployee()。这个函数用来在模型空间(MODELSPACE)的(10,10,0)点处创建一个半径为2.0的圆:
[CommandMethod("test")]
static public void voidcreateCircle()
{
//首先声明我们要使用的对象
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;
Editor ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
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();
}
}
运行你的代码来进行测试……
上面的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()只是关闭它们。
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(表示黄色)。
回到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) 最后,我们要创建一个位于模型空间的块索引,它表示上面创建的块的一个实例。这一步留给大家练习。
下面是你要遵循的最基本的步骤:
创建一个名为CreateEmployee新的函数
把命令属性“CREATE”移动到CreateEmployee()
修改CreateEmployeeDefintion()来返回新创建的块“EmployeeBlock”的ObjectId,操作的步骤请参考CreateLayer()的作法。
你需要修改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
{
…
在新创建的CreateEmployee()函数中创建一个新的BlockReference对象,并把它加入到模型空间。提示:我们可以使用CreateEmployeeDefinition()中引用模型空间的代码,这些代码在这里不需要了
在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的代码。
我们首先要做的是定义一个新的函数,它用来在命名对象字典(NOD)中创建公司字典对象。为这个函数取名为CreateDivision(),,并使用命令属性来定义CREATEDIVISION命令。
下面是这个函数的代码,它的形式非常简单,只是用来在NOD中创建一个ACME_DIVISION(用来表示公司)
[CommandMethod("CreateDivision")]
public void CreateDivision()
{
Databasedb = HostApplicationServices.WorkingDatabase;
Transactiontrans = 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工具)
接下来,我们要在ACME_DIVISION字典中加入销售(Sales)条目。销售(Sales)条目同样也是一个字典。由于销售(Sales)字典与ACME_DIVISION字典的关系如同ACME_DIVISION字典与NOD,所以代码是类似的。定义下面的代码部分在ACME_DIVISION字典中创建一个名为’Sales’的字典。
代码提示:
DBDictionarydivDict;
try
{
divDict =(DBDictionary)trans.GetObject(acmeDict.GetAt("Sales"), OpenMode.ForWrite);
}
catch
…
运行函数来看‘Sales’条目是否已加入到ACME_DIVISION字典。
现在我们要在这个字典中加入一个特殊的记录,它可以包含任意的自定义数据。我们要加入的数据类型为扩展记录(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,我们使用它来表示扩展记录链是从什么地方开始的。
所以接下来的代码块看起来和前面两个非常相似:
XrecordmgrXRec;
try
{
mgrXRec= (Xrecord)trans.GetObject(divDict.GetAt("Department Manager"),OpenMode.ForWrite);
}
catch
{
mgrXRec = new Xrecord();
mgrXRec.Data = newResultBuffer(new TypedValue((int)DxfCode.Text, "Randolph P.Brokwell"));
divDict.SetAt("DepartmentManager", 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();
DBDictionarybrExtDict =(DBDictionary)trans.GetObject(br.ExtensionDictionary, OpenMode.ForWrite,false);
由于扩展字典也是字典,我们可以和第3步一样在扩展字典中加入扩展记录。请完成有关的代码来创建和访问块索引的扩展字典,加入你在第4步中创建的扩展记录,然后把扩展记录加入到事务处理。
6) 返回到NOD……因为在NOD中创建公司字典只需要一次(就像创建Employee块一样),因此我们应该把CreateDivision函数的命令属性去掉,而在CreateEmployeeDefinition()中调用这个函数。请自己完成这些改变。当所有这些都做完后,当CREATE命令第一次运行的时候,所有的函数都会被调用。
7) 下面的步骤和上面的无关。我们将创建一个函数来遍历模型空间,以用来查找加入的Employee对象(这里其实是块索引)的数目。在VB.NET 或C#中,我们可以把模型空间块表记录(ModelSpaceBlockTableRecord)当作一个集合,这样就可以使用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 BlockReferenceThen
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对象的服务。要使用它,请加入下面的代码:
ImportsAutodesk.AutoCAD.EditorInput
ImportsAutodesk.AutoCAD.ApplicationServices
在函数的内部:
Editor ed =Application.DocumentManager.MdiActiveDocument.Editor;
最后,在循环的后面确定找到了多少个块索引:
ed.WriteMessage("EmployeesFound: " + nEmployeeCount.ToString());
第四章结束
下面的代码片断演示了怎样获取Employee对象的所有内容,包括ACME_DIVISION字典中的部门经理的名字。这部分要在后面的章节中使用,但因为它和本章有关,因此我们把它放在本章作介绍。如果有时间的话,请阅读一下其中的代码来看看它是怎么使用的。它可以被直接放到你的类中并可以运行。命令的名字是PRINTOUTEMPLOYEE。ListEmployee()函数接收一个ObjectId参数,它通过一个ref类型的字符串数组返回值(包含相应的雇员数据)。调用它的PrintoutEmployee()函数只是用来在命令行中输出这些数据。
我们需要一个遍历并显示所有雇员数据的命令。
public staticvoid 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 = newString[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 (ObjectIdid 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("EmployeeName: {0}", saEmployeeList[0]);
ed.WriteMessage("EmployeeSalary: {0}", saEmployeeList[1]);
ed.WriteMessage("EmployeeDivision: {0}", saEmployeeList[2]);
ed.WriteMessage("DivisionManager: {0}", saEmployeeList[3]);
}
}
}
}
finally
{
}
}