长春's profile长春的共享空间PhotosBlogLists Tools Help
Lists
Photo 1 of 1
More albums (1)

我的小窝

Loading...Loading...

长春的共享空间

Create For C# & DotNet
October 23

调试Windows Live Writer

本站志在研究SEO、SEM,正所谓磨刀不误砍柴工,先调试一下开博的软件。希望能一次成功。
March 15

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属性。你会发现它有七种不同的形式。在上面的例子中,我们使用了最简单的形式,它只有一个输入参数(命令的名字)。你可以使用其它的形式来控制命令的工作方式,例如你可以确定命令组的名字、全局和局部名字、命令标识(命令如何来运行)等。 
第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());
  
   } 
第 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层? 
第 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
  
   {
  
   }
  
  }
背景
  提示通常包含一个描述性信息,伴随一个停止以让用户理解所给的信息并输入数据。数据可以通过多种方式被输入,如通过命令行、对话框或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);
  
  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");
  }

用C#生成随机中文汉字验证码的基本原理

下面介绍一下使用C#生成随机的中文汉字的原理。
  
  
  1、汉字编码原理
  到底怎么办到随机生成汉字的呢?汉字从哪里来的呢?是不是有个后台数据表,其中存放了所需要的所有汉字,使用程序随机取出几个汉字组合就行了呢?使用后台数据库先将所有汉字存起来使用时随机取出,这也是一种办法,但是中文汉字有这么多,怎么来制作呢?其实可以不使用任何后台数据库,使用程序就能做到这一切。要知道如何生成汉字,就得先了解中文汉字的编码原理。
  1980年,为了使每一个汉字有一个全国统一的代码,我国颁布了第一个汉字编码的国家标准: GB2312-80《信息交换用汉字编码字符集》基本集,简称GB2312,这个字符集是我国中文信息处理技术的发展基础,也是国内所有汉字系统的统一标准。到了后来又公布了国家标准GB18030-2000《信息交换用汉字编码字符集基本集的扩充》,简称GB18030,编程时如果涉及到编码和本地化的朋友应该对GB18030很熟悉。这是是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,同时也是未来我国计算机系统必须遵循的基础性标准之一。
  目前在中文WINDOWS操作系统中,.NET编程中默认的的代码页就是GB18030简体中文。但是事实上如果生成中文汉字验证码只须要使用GB2312字符集就已经足够了。字符集中除了我们平时大家都认识的汉字外,也包含了很多我们不认识平时也很少见到的汉字。如果生成中文汉字验证码中有很多我们不认识的汉字让我们输入,对于使用拼音输入法的朋友来说可不是好事,五笔使用者还能勉强根据汉字的长相打出来,呵呵!所以对于GB2312字符集中的汉字我们也不是全都要用。
  中文汉字字符可以使用区位码来表示,见
  
  汉字区位码表 http://navicy2005.home4u.china.com/resource/gb2312tbl.htm
  汉字区位码代码表 http://navicy2005.home4u.china.com/resource/gb2312tbm.htm
  
  其实这两个表是同一回事,只不过一个使用十六进制分区表示,一个使用区位所在的数字位置表示。 例如“好”字的十六进制区位码是ba c3,前两位是区域,后两位代表位置,ba处在第26区,“好”处在此区汉字的第35位也就是c3位置,所以数字代码就是2635。这就是GB2312汉字区位原理。根据《汉字区位码表 》我们可以发现第15区也就是AF区以前都没有汉字,只有少量符号,汉字都从第16区B0开始,这就是为什么GB2312字符集都是从16区开始的。
  
  2、.Net程序处理汉字编码原理分析
  在.Net中可以使用System.Text来处理所有语言的编码。在System.Text命名空间中包含众多编码的类,可供进行操作及转换。其中的Encoding类就是重点处理汉字编码的类。通过在.NET文档中查询Encoding类的方法我们可以发现所有和文字编码有关的都是字节数组,其中有两个很好用的方法:
  
  
  
  Encoding.GetBytes ()方法将指定的 String 或字符数组的全部或部分内容编码为字节数组
  Encoding.GetString ()方法将指定字节数组解码为字符串。
  
  
  没错我们可以通过这两个方法将汉字字符编码为字节数组,同样知道了汉字GB2312的字节数组编码也就可以将字节数组解码为汉字字符。通过对“好”字进行编码为字节数组后
  
  
  
  Encoding gb=System.Text.Encoding.GetEncoding("gb2312");
  object[] bytes=gb.Encoding.GetBytes ("好");
  
  
  发现得到了一个长度为2的字节数组bytes,使用
  
  
  
  string lowCode = System.Convert.ToString(bytes[0], 16); //取出元素1编码内容(两位16进制)
  string hightCode = System.Convert.ToString(bytes[1], 16);//取出元素2编码内容(两位16进制)
  
  
  之后发现字节数组bytes16进制变码后内容竟然是{ba,c3},刚好是“好”字的十六进制区位码(见区位码表)。
  因此我们就可以随机生成一个长度为2的十六进制字节数组,使用GetString ()方法对其进行解码就可以得到汉字字符了。不过对于生成中文汉字验证码来说,因为第15区也就是AF区以前都没有汉字,只有少量符号,汉字都从第16区B0开始,并且从区位D7开始以后的汉字都是和很难见到的繁杂汉字,所以这些都要排出掉。所以随机生成的汉字十六进制区位码第1位范围在B、C、D之间,如果第1位是D的话,第2位区位码就不能是7以后的十六进制数。在来看看区位码表发现每区的第一个位置和最后一个位置都是空的,没有汉字,因此随机生成的区位码第3位如果是A的话,第4位就不能是0;第3位如果是F的话,第4位就不能是F。
  好了,知道了原理,随机生成中文汉字的程序也就出来了,以下就是生成4个随机汉字的C#控制台代码:
  
  
  3、程序代码:
  
  
  
  
  using System;
  using System.Text;
  
  namespace ConsoleApplication
  {
   class ChineseCode
    {
   public static void Main()
   {
   //获取GB2312编码页(表)
   Encoding gb=Encoding.GetEncoding("gb2312");
  
   //调用函数产生4个随机中文汉字编码
   object[] bytes=CreateRegionCode(4);
  
   //根据汉字编码的字节数组解码出中文汉字
   string str1=gb.GetString((byte[])Convert.ChangeType(bytes[0], typeof(byte[])));
   string str2=gb.GetString((byte[])Convert.ChangeType(bytes[1], typeof(byte[])));
   string str3=gb.GetString((byte[])Convert.ChangeType(bytes[2], typeof(byte[])));
   string str4=gb.GetString((byte[])Convert.ChangeType(bytes[3], typeof(byte[])));
  
   //输出的控制台
     Console.WriteLine(str1 + str2 +str3 +str4);
     }
  
  
   /**//*
   此函数在汉字编码范围内随机创建含两个元素的十六进制字节数组,每个字节数组代表一个汉字,并将
   四个字节数组存储在object数组中。
   参数:strlength,代表需要产生的汉字个数
   */
   public static object[] CreateRegionCode(int strlength)
   {
   //定义一个字符串数组储存汉字编码的组成元素
   string[] rBase=new String [16]{"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
  
   Random rnd=new Random();
  
   //定义一个object数组用来
   object[] bytes=new object[strlength];
  
   /**//*每循环一次产生一个含两个元素的十六进制字节数组,并将其放入bject数组中
   每个汉字有四个区位码组成
   区位码第1位和区位码第2位作为字节数组第一个元素
   区位码第3位和区位码第4位作为字节数组第二个元素
   */
   for(int i=0;i<strlength;i++)
   {
   //区位码第1位
   int r1=rnd.Next(11,14);
   string str_r1=rBase[r1].Trim();
  
   //区位码第2位
   rnd=new Random(r1*unchecked((int)DateTime.Now.Ticks)+i);//更换随机数发生器的
  
  种子避免产生重复值
   int r2;
   if (r1==13)
   {
   r2=rnd.Next(0,7);
   }
   else
   {
   r2=rnd.Next(0,16);
   }
   string str_r2=rBase[r2].Trim();
  
   //区位码第3位
   rnd=new Random(r2*unchecked((int)DateTime.Now.Ticks)+i);
   int r3=rnd.Next(10,16);
   string str_r3=rBase[r3].Trim();
  
   //区位码第4位
   rnd=new Random(r3*unchecked((int)DateTime.Now.Ticks)+i);
   int r4;
   if (r3==10)
   {
   r4=rnd.Next(1,16);
   }
   else if (r3==15)
   {
   r4=rnd.Next(0,15);
   }
   else
   {
   r4=rnd.Next(0,16);
   }
   string str_r4=rBase[r4].Trim();
  
   //定义两个字节变量存储产生的随机汉字区位码
   byte byte1=Convert.ToByte(str_r1 + str_r2,16);
   byte byte2=Convert.ToByte(str_r3 + str_r4,16);
   //将两个字节变量存储在字节数组中
   byte[] str_r=new byte[]{byte1,byte2};
  
   //将产生的一个汉字的字节数组放入object数组中
   bytes.SetValue(str_r,i);
  
   }
  
   return bytes;
  
   }
    }
  
  }
  
  
  
  实现了随机生成汉字后,就可以使用.NET GDI来绘制自己需要的验证码图形了。具体的怎样生成验证码图片,以及改变其中字符的长和宽等效果网上已经有很多相关的文章,这里由于篇幅就不再介绍了。不过有一点要说明的是以上代码在中文版的Windows下才能运行,因为它带有GB的字符集,如果你是其他语言的操作系统,就需要安装GB字符集了。

ASP.net 验证码(C#)

    public class ValidateCode : System.Web.UI.Page
   {
   private void Page_Load(object sender, System.EventArgs e)
   {
   this.CreateCheckCodeImage(GenerateCheckCode());
   }
  
   #region web 窗体设计器生成的代码
   override protected void OnInit(EventArgs e)
   {
   //
   // CODEGEN: 该调用是 asp.NET web 窗体设计器所必需的。
   //
   InitializeComponent();
   base.OnInit(e);
   }
  
   /// <summary>
   /// 设计器支持所需的方法 - 不要使用代码编辑器修改
   /// 此方法的内容。
   /// </summary>
   private void InitializeComponent()
   {
   this.Load += new System.EventHandler(this.Page_Load);
   }
   #endregion
  
   private string GenerateCheckCode()
   {
   int number;
   char code;
   string checkCode = String.Empty;
  
   System.Random random = new Random();
  
   for(int i=0; i<5; i++)
   {
   number = random.Next();
  
   if(number % 2 == 0)
   code = (char)('0' + (char)(number % 10));
   else
   code = (char)('A' + (char)(number % 26));
  
   checkCode += code.ToString();
   }
  
   Response.Cookies.Add(new HttpCookie("CheckCode", checkCode));
  
   return checkCode;
   }
  
   private void CreateCheckCodeImage(string checkCode)
   {
   if(checkCode == null || checkCode.Trim() == String.Empty)
   return;
  
   System.Drawing.Bitmap image = new System.Drawing.Bitmap((int)Math.Ceiling((checkCode.Length * 12.5)), 22);
   Graphics g = Graphics.FromImage(image);
  
   try
   {
   //生成随机生成器
   Random random = new Random();
  
   //清空图片背景色
   g.Clear(Color.White);
  
   //画图片的背景噪音线
   for(int i=0; i<25; i++)
   {
   int x1 = random.Next(image.Width);
   int x2 = random.Next(image.Width);
   int y1 = random.Next(image.Height);
   int y2 = random.Next(image.Height);
  
   g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
   }
  
   Font font = new System.Drawing.Font("Arial", 12, (System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic));
   System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2f, true);
   g.DrawString(checkCode, font, brush, 2, 2);
  
   //画图片的前景噪音点
   for(int i=0; i<100; i++)
   {
   int x = random.Next(image.Width);
   int y = random.Next(image.Height);
  
   image.SetPixel(x, y, Color.FromArgb(random.Next()));
   }
  
   //画图片的边框线
   g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1);
  
   System.IO.MemoryStream ms = new System.IO.MemoryStream();
   image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
   Response.ClearContent();
   Response.ContentType = "image/Gif";
   Response.BinaryWrite(ms.ToArray());
   }
   finally
   {
   g.Dispose();
   image.Dispose();
   }
   }
   }

C#的四个基本技巧

.如果可能尽量使用接口来编程
  .NET框架包括类和接口,在编写程序的时候,你可能知道正在用.NET的哪个类。然而,在这种情况下如果你用.NET支持的接口而不是它的类来编程时,代码会变得更加稳定、可用性会更高。请分析下面的代码:
  这个函数从一个可为任何对象的数组中加载ListBox,这段代码被限定为只能使用数组。假想过些时候你发现那些对象存在数据库中,或别的集合中。那么你需要修改程序来使用不同的集合类型。如果你用ICollection接口来写那段程序,你就不用修改那段程序了,对于任何实现ICollection接口的类型它都能很好的工作:

  ICollection被数组和所有System.Collection中的集合实现。此外,多维数组也支持ICollection接口。如果那还不够的话,数据库.NET类同样支持ICollection接口。用接口写的这个函数不用需改就可以才许多中情况下使用。
  2. 使用属性代替原始数据
  因为属性已经成为语言本身的元素,所以声明数据元素时它的作用域等级没有必要大于private。因为代码本身会把属性看成数据元素,你并没有失去使用简单数据类型的便利性 。相反它会使你的代码更加灵活功能更加强大。属性使你的数据元素封装性更好。属性可以让你使用lazy evaluation来返回数据。lazy evaluation的意思是当用户请求时才计算它的值,而不是一直保留着它。
  最后,属性可以是virtual也可以是abstract。你也可以在接口中定义属性。
  这里还有维护方面的因素应当注意:尽管操作两者的方法是一样的,但是你把一个数据元素变成属性,那么原先客户端的程序便不能访问服务端的新版本程序了。实际上对于在Web service中你想实现序列化的值你可以把它们变成属性来使用:
private int TheMonth = 0;

[XmlAttribute ("Month")]
public int Month
{
 get {
  return TheMonth;
 }
 set {
  TheMonth = value;
 }
}

  简单通过属性就可以使你的所有数据元素私有化。

  3. 在Producer/Consumer 的Idiom中使用Delegate

  当你生成一个实现producer idiom类的时候,使用deletate来通知consumer。这种方法相对于用接口更加灵活。Delegate是多点传送的,所以不用加额外的代码你就何以支持多用户。相对于用接口这样做可使类之间的耦合性降低。

  下面的类处理键盘输入并把它传给所有的registered listeners:

public class KeyboardProcessor
{
private OnGetLine theFunc = null;

public OnGetLine OnGetLineCallback {
 get {
  return theFunc;
 }
 set {
  theFunc = value;
 }
}

public void Run (){
// Read input.
// If there is any listeners, publish:
string s;
do {
 s = Console.ReadLine ();
 if (s.Length == 0)
  break;
 if (theFunc != null){
  System.Delegate [] funcs =theFunc.GetInvocationList();
  foreach (OnGetLine f in funcs) {
   try {
    f (s);
   } catch (Exception e) {
    Console.WriteLine
    ("Caught Exception: {0}", e.Message);
   }
  }
 }
} while (true);
}

  任何数目的listeners都可注册到producer,它们所要做的只是提供一个特定的函数:deletate。