第 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層?
==========================================================================================
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
|
|
最後,在循環的後面確定找到了多少個塊索引:
背景
提示通常包含一個描述性信息,伴隨一個停止以讓用戶理解所給的信息並輸入數據。數據可以通過多種方式被輸入,如通過命令行、對話框或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");
}