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>:
如果使用者想要再次選擇預設的名字,他可以按Enter鍵。
讓我們建立用于職位提示的關鍵字清單:
//加入用于職位提示的關鍵字
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()函數在指令行中輸出雇員的詳細資料。
下面是你必須遵循的步驟:
1. 調用“LISTEMPLOYEES”指令
2. 調用Editor的GetSelection()函數來選擇實體
PromptSelectionResult res = ed.GetSelection(Opts, filter);
3. 上面的filter用來過濾選擇集中的塊索引。你可以建立如下的過濾清單:
TypedValue[] filList = new TypedValue[1];
filList[0] = new TypedValue((int)DxfCode.Start, "INSERT");
SelectionFilter filter = new SelectionFilter(filList);
4. 從選擇集中擷取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");
}
Autodesk官方最新的.NET教程(六)(C#版)
第6章 更多的使用者界面:添加自定義資料
在本章中,我們将介紹.NET API的使用者界面部分能做些什麼。我們首先将介紹一個自定義上下文菜單(快捷菜單)。接下來我們将實作一個無模式可停靠的面闆(一個真正的AutoCAD增強輔助視窗)來支援拖放操作。接着我們将介紹通過模式窗體選取實體。最後,我們将介紹使用AutoCAD的選項對話框來設定雇員的預設值。
本章還會介紹和上面内容有關的API。
第一部分 自定義上下文菜單
到目前為止,我們所寫的代碼隻與CommandMethod屬性定義的指令行進行互相操作。一個AutoCAD .NET程式能通過一個特殊的類來實作裝載時的初始化工作。這個類隻要實作IExtensionApplication .NET接口并暴露一個元件級别的屬性(此屬性把類定義為ExtensionApplication),就可以響應一次性的裝載和解除安裝事件。例子:
[assembly: ExtensionApplication(typeof(Lab6_CS.AsdkClass1))]
public classAsdkClass1 : IExtensionApplication
{
1) 現在修改AsdkClass1類來實作上面的接口。要實作這個接口,你必須實作Initialize() 和Terminate()函數。因為我們要實作的是一個接口,而接口中的函數總是定義為純虛拟的。
public voidInitialize()
{
AddContextMenu();
EmployeeOptions.AddTabDialog();
}
public voidTerminate()
{
}
為了加入自定義上下文菜單,我們必須定義一個‘ContextMenuExtension’類的成員。這個類位于Autodesk.AutoCAD.Windows命名空間中。
要使用ContextMenuExtension,我們必須使用new關鍵字來進行初始化,給必要的屬性指派,并調用Application.AddDefaultContextMenuExtension()。上下文菜單的工作方式是:對于每個菜單條目,我們定義一個成員函數來處理菜單單擊事件。我們可能通過.NET的代理來實作。我們使用C#關鍵字+=和-=确定讓哪個函數來處理事件。請盡快熟悉這種設計模式,因為在C#中會使用很多。
2) 添加一個‘ContextMenuExtension’成員變量和下面兩個用來添加和移除自定義菜單的函數。請好好研究一下代碼來看看究竟發生了什麼。
void AddContextMenu()
{
try
{
m_ContextMenu= new ContextMenuExtension();
m_ContextMenu.Title= "Acme Employee Menu";
Autodesk.AutoCAD.Windows.MenuItemmi;
mi= newAutodesk.AutoCAD.Windows.MenuItem("Create Employee");
mi.Click+= newEventHandler(CallbackOnClick);
m_ContextMenu.MenuItems.Add(mi);
Autodesk.AutoCAD.ApplicationServices.Application.AddDefaultContextMenuExtension(m_ContextMenu);
}
catch
{
}
}
void RemoveContextMenu()
{
try
{
if( m_ContextMenu != null)
{
Autodesk.AutoCAD.ApplicationServices.Application.RemoveDefaultContextMenuExtension(m_ContextMenu);
m_ContextMenu= null;
}
}
catch
{
}
}
注意我們在代碼中使用了‘CallbackOnClick’函數。我們希望這個函數(我們現在還沒有定義)響應菜單項選擇事件。在我們的例子中,我們想要做的是調用我們的成員函數‘Create()’。請加入下面的代碼。
void CallbackOnClick(objectSender, EventArgs e)
{
Create();
}
現在,從Initialize()中調用AddContextMenu()函數,同樣地,請在Terminate()中調用RemoveContextMenu()。
請運作代碼。使用NETLOAD來裝載編譯好的元件,然後在AutoCAD的空白處右擊……你應該可以看到’Acme‘快捷菜單了。如果失敗了,明明你做的都是正确的……為什麼呢?
通常,AutoCAD的資料是存儲在文檔中的,而通路實體的指令有權修改文檔。當我們運作代碼來響應上下文菜單單擊事件,我們是從指令結構的外部來通路文檔。當我們調用的代碼嘗試通過添加一個雇員來修改文檔時,我們就碰到了錯誤。正确的做法是必須鎖住文檔,這可以通過使用Document.LockDocument()指令來實作。
3) 修改CallbackOnClick來鎖住文檔:
void CallbackOnClick(objectSender, EventArgs e)
{
DocumentLockdocLock =Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.LockDocument();
Create();
docLock.Dispose();
}
注意,我們保留了一個‘DocumentLock’對象的拷貝。要把文檔解鎖,我們隻要銷毀這個DocumentLock對象。
再次運作代碼。現在應該可以看到快捷菜單了。
第2部分 無模式對話框、可進行拖放的可停靠面闆
為了使我們的使用者界面和AutoCAD實作無縫連結,我們要盡可能在所有的地方使用同樣的使用者界面結構。這會使應用程式看起來與AutoCAD結合的很好,并有效地減少代碼的重複。一個很好的例子是AutoCAD中的可停靠面闆。
使用.NET API,我們可以建立一個簡單的窗體并把它放到面闆中。我們可以執行個體化一個自定義的‘PaletteSet’對象來包含窗體,并可以把這個PaletteSet定義成我們喜歡的樣式。
4) 在解決方案浏覽器中通過右擊工程來添加一個使用者控件。給它命名為ModelessForm。使用控件工具箱,加入如下所示的編輯框和标簽控件。
使用屬性視窗設定三個編輯框的屬性。設定如下:
<首先是最上面的編輯框>
(Name) = tb_Name
Text = <請輸入一個名字>
<第二個編輯框>
(Name) = tb_Division
Text = Sales
<第三個編輯框>
(Name) = tb_Salary
Text = <請輸入薪水>
要使用.NET API執行個體化一個面闆對象,你必須要執行個體化使用者控件對象(無模式窗體)和‘PaletteSet’對象。調用PaletteSet的成員函數Add來傳遞使用者控件對象。
5) 接下來,我們要加入一個指令來建立面闆。在類中加入一個名為CreatePalette的函數和CommandMethod屬性來定義名為“PALETTE”的指令。
請看一下下面的代碼塊。這是執行個體化面闆的代碼。
ps = new Autodesk.AutoCAD.Windows.PaletteSet("TestPalette Set");
ps.MinimumSize = newSystem.Drawing.Size(300, 300);
System.Windows.Forms.UserControl myCtrl = new ModelessForm();
ps.Add("test", myCtrl);
ps.Visible = true;
6) 把上面的代碼加入到CreatePalette()函數。‘ps’需要在函數的外部聲明:
private Autodesk.AutoCAD.Windows.PaletteSet ps;
在函數的執行個體化面闆代碼之前加入檢查ps是否為null的代碼。
編譯并運作工程。在AutoCAD中裝載元件,運作‘PALETTE’指令來檢查面闆是否被裝載。
使用PaletteSet.Style來看看PaletteSetStyles對象。例如:
ps.Style = PaletteSetStyles.ShowTabForSingle;
我們也可以試試諸如透明性的屬性,例如:
ps.Opacity =90;
注意:要使用PaletteSet 和PaletteSetStyles對象,你必須加入兩個命名空間Autodesk.AutoCAD.Windows和Autodesk.AutoCAD.Windows.Palette
在我們繼續之前,讓我們執行一個快速的維護更新:請在AsdkClass1類中加入下列成員:
public static string sDivisionDefault = "Sales";
public static string sDivisionManager = "Fiona Q.Farnsby";
這些值将被用作為部門和部門經理的預設值。由于它們被聲明為’static’,它們在每個程式中隻執行個體化一次,并在元件裝載的時候執行個體化。
第2a部分 在無模式窗體中加入拖放支援
在這部分,我們将加入允許我們使用面闆窗體中編輯框的值來建立一個雇員。當使用者從面闆中拖動到AutoCAD中,将會提示輸入職位,一個新的雇員實體将使用這些值來進行建立。
7) 為了支援拖放,我們首先需要一個對象來進行拖動。在編輯框的下面,另外加入一個名為Label4的标簽控件,設定标簽的文本為一些提示性的東西(‘Drag to Create Employee’)。通過這個标簽,我們可以在AutoCAD中處理拖放。
要捕捉到什麼時候拖動事件發生,我們必須要知道什麼時候滑鼠開始操作。
首先,我們要在類的構造函數中注冊事件,代碼如下:
Label4.MouseMove+= newSystem.Windows.Forms.MouseEventHandler(Label4_MouseMove);
8) 在ModelessForm類中加入下面的函數聲明:
private voidLabel4_MouseMove( object sender,System.Windows.Forms.MouseEventArgs e)
{
if (System.Windows.Forms.Control.MouseButtons ==System.Windows.Forms.MouseButtons.Left)
{
// start dragDrop operation, MyDropTarget will be calledwhen the cursor enters the AutoCAD view area.
Autodesk.AutoCAD.ApplicationServices.Application.DoDragDrop(this, this,System.Windows.Forms.DragDropEffects.All, newMyDropTarget());
}
}
通常事件處理器有2個輸入參數,一個object類的sender和與事件有關的參數。對于MouseMove,我們也要做同樣的事情。
運作這個工程,檢查一下當滑鼠經過文本的時候,函數是否被調用的。
我們還可以進一步知道是不是按了滑鼠左鍵:
if (System.Windows.Forms.Control.MouseButtons ==System.Windows.Forms.MouseButtons.Left)
{
}
我們需要一個方法來檢測什麼時候對象被拖入到AutoCAD。我們可以使用.NET的基類DropTarget來實作。要使用它,你隻要建立從這個基類派生的類并實作你想要的函數。在我們這個例子中,我們需要的是OnDrop()。
9) 在工程中加入一個從Autodesk.AutoCAD.Windows.DropTarget派生的類‘MyDropTarget’。如果你把這個類加入到ModelessForm.cs檔案中,請把這個類加入到ModelessForm類之後。
override public void OnDrop(System.Windows.Forms.DragEventArgs e)
{
}
在這個函數中,我們最後會調用AsdkClass1的成員CreateDivision() 和CreateEmployee,傳入ModelessForm類中的編輯框的值。要實作這個功能,我們需要一個方法來連接配接ModelessForm執行個體。最佳的方法是通過DragEventArgs。但首先我們要把滑鼠事件連接配接到MyDropTarget類。
10) 加入下面的代碼到滑鼠左鍵(MouseButtons.Left)處理函數中:
Autodesk.AutoCAD.ApplicationServices.Application.DoDragDrop(this, this,System.Windows.Forms.DragDropEffects.All, newMyDropTarget());
注意我們傳入’this’兩次。第一次是用于Control參數,第二次是用于傳入使用者自定義資料。因為我們傳入的是ModelessForm 類的執行個體,是以我們可以在放下的時候使用它來擷取編輯框的值。
11) 回到OnDrop處理函數,讓我們使用參數來調用建立雇員的函數。首先,添加職位提示的代碼。在AsdkClass1.Create()中已經有相關的代碼了,位于‘Get EmployeesCoordinates…’.注釋下面。添加此代碼來提示輸入職位。
12) 接下來,擷取傳入到DragEventArgs 參數的ModelessForm對象:
ModelessFormctrl = (ModelessForm)e.Data.GetData(typeof(ModelessForm));
請注意一下怎樣通過typeof關鍵字把參數強制轉化為ModelessForm的執行個體。
13) 使用上面的執行個體來調用AsdkClass1成員:
AsdkClass1.CreateDivision(ctrl.tb_Division.Text,AsdkClass1.sDivisionManager);
AsdkClass1.CreateEmployee(ctrl.tb_Name.Text,ctrl.tb_Division.Text, Convert.ToDouble(ctrl.tb_Salary.Text), prPosRes.Value);
注意:AsdkClass1的方法要不通過AsdkClass1的執行個體來調用,那麼方法必須被聲明為’ public static’。因為public static 方法隻能調用其它的public static 方法,你需要修改幾個AsdkClass1類中的方法為’ public static’。請你進行相關的修改(應該至少有4項要修改)。
14) 最後,因為我們處理的事件位于AutoCAD指令之外,我們必須再次在會修改資料庫的代碼處鎖住文檔。請加入鎖住文檔的代碼,加入的方法與前面的上下文菜單是一樣的。
編譯、裝載并運作元件,使用PALETTE指令。你應該可以使用拖放操作來建立一個雇員了。
第三部分 從有模式窗體中選擇實體
本章的以下部分将示範擷取一個使用者在螢幕上選擇的雇員執行個體的詳細資訊,并把資訊顯示在一個有模式窗體的編輯框中。這部分的重點是建立一個有模式窗體,并在執行選擇操作而窗體要失去焦點時隐藏它。為了擷取雇員的詳細資訊,我們将使用第4章結束時給出的ListEmployee幫助函數。
首先,我們需要建立一個窗體類。這個類是一個真實的窗體而不是我們在ModelessForm中建立的使用者控件。
15) 在工程中建立一個Windows窗體類。調用‘ModalForm’類。在窗體中加入以下所示的三個編輯框控件和标簽控件以及兩個按鈕。
使用屬性視窗來設定三個編輯框的屬性。設定如下:
<首先是最上面的編輯框>
(Name) = tb_Name
Text = <空白>
<第二個編輯框>
(Name) = tb_Division
Text = <空白>
<第三個編輯框>
(Name) = tb_Salary
Text = <空白>
<上部的按鈕>
(Name) = SelectEmployeeButton
Text= Select Employee
<下部的按鈕>
(Name) = Close
Text= Close
接下來建立按鈕的事件處理函數。‘Close’按鈕可以隻簡單地調用:
this.Close();
要顯示對話框,讓我們在類中建立一個把窗體執行個體化為有模式對話框的指令函數。下面的實作的代碼:
[CommandMethod("MODALFORM")]
public voidShowModalForm()
{
ModalFormmodalForm = new ModalForm();
Autodesk.AutoCAD.ApplicationServices.Application.ShowModalDialog(modalForm);
}
編譯、裝載并在AutoCAD中運作MODALFORM指令來看看對話框是否可以工作。試試在對話框的右下角調整對話框的大小,然後關閉它。注意,重新使用MODALFORM指令時,對話框會出現在你上次離開的地方!這是ShowModalDialog方法的一個特征。大小和位置值被AutoCAD儲存了。
‘Select Employee’按鈕首先将執行一個簡單的實體選擇。這我們可以通過使用Editor.GetEntity()方法來實作,選擇單一的實體比使用選擇集來得友善的多。下面是怎樣使用這個方法的代碼:
PromptEntityOptionsprEnt = new PromptEntityOptions("Select anEmployee");
PromptEntityResultprEntRes = ed.GetEntity(prEnt);
16) 把上面的代碼加入到SelectEmployeeButton_Click處理函數中,還要加入必需的資料庫、指令行、事務處理設定變量和一個try catch塊。不要忘了在finally塊中銷毀它們。
使用PromptStatus.OK來測試GetEntity的傳回值,如果傳回不等于,就調用this.Show并退出處理函數。
一旦我們獲得的傳回值是OK,那麼我們就可以使用PromptEntityResult.ObjectId()方法來擷取所選實體的object Id。這個id可以和一個固定的字元串數組被傳入到AsdkClass1.ListEmployee函數中來擷取雇員的詳細資訊。可以通過以下的代碼說明:
ArrayListsaEmployeeList = new ArrayList();
AsdkClass1.ListEmployee(prEntRes.ObjectId,saEmployeeList);
if (saEmployeeList.Count == 4)
{
tb_Name.Text= saEmployeeList[0].ToString();
tb_Salary.Text= saEmployeeList[1].ToString();
tb_Division.Text= saEmployeeList[2].ToString();
}
17) 加入上面的代碼,它會在窗體的編輯框中顯示雇員的詳細資訊。
在開始測試代碼之前,我們還要記住的是代碼是在有模式對話框中運作的,也就意味着當對話框可見的時候使用者與AutoCAD的互操作是被禁止的。在使用者能夠進行選擇雇員對象之前,我們必須隐藏窗體。當選擇結束後,我們可以再次站窗體顯示(例如,可以在finally塊的函數中)
18) 在選擇之前加入隐藏窗體的代碼(例如在try塊之前) ‘this.Hide’ 和選擇結束後顯示窗體的代碼(例如,可以在finally塊中)‘this.Show’。
編譯、裝載并在AutoCAD中運作MODALFORM指令來看看對話框是否工作。試試選擇一個實體并填充窗體中編輯框的值。
第四部分 在AutoCAD選項對話框中加入頁面
本章的最後部分将向你介紹如何定義一個使用者控件,這個控件可以被作為一個頁面顯示在AutoCAD的選項對話框中。我們可以使用這個頁面來設定程式運作期間的預設值。在Employee例子中,我們隻是在AsdkClass1類中簡單地設定了sDivisionDefault 和sDivisionManager字元串。
19) 在工程中加入另外一個名為‘EmployeeOptions’的使用者控件。在控件中加入兩個編輯框和标簽控件,如下圖所示:
使用屬性視窗來設定編輯框的屬性,設定如下:
<上面的編輯框>
(Name) = tb_EmployeeDivision
Text = <空白>
<下面的編輯框>
(Name) = tb_DivisionManager
Text = <空白>
使用.NET API來顯示自定義多頁對話框,需要兩個步驟。首先,通過傳入要調用的成員函數的位址,來知道什麼時候選項對話框出現。其次是實作回調函數。傳入到回調函數中的第二個參數是一個‘TabbedDialogEventArgs’對象,我們必須使用它來調用‘AddTab’函數。AddTab使用一個标題字元串和一個‘TabbedDialogExtension’對象的執行個體,此執行個體封裝了我們的窗體(其實是使用者控件)。在TabbedDialogExtension的構造函數中,我們輸入窗體的執行個體和回調函數(OnOK, OnCancel 或OnHelp)的位址。
20) 在EmployeeOptions類中,加入一個名為AddTabDialog的public static函數,它會添加一個可供系統調用的事件處理:
public static void AddTabDialog()
{
Autodesk.AutoCAD.ApplicationServices.Application.DisplayingOptionDialog+= new TabbedDialogEventHandler(TabHandler);
}
在AsdkClass1的Initialize函數中加入調用此函數的代碼。因為這個函數是在程式啟動的時候調用的(因為類已經實作了IExtensionApplication接口),是以多頁對話框就被自動的加載。
20a) 實作一個相同的函數來移除事件處理,使用C#的-=關鍵字。
在這裡,你可以看到我們為AutoCAD中的Application 對象的DisplayingOptionDialog事件加入了一個處理函數,此函數會調用‘TabHandler’函數。是以接下來我們要實作這個函數。
21) 加入下面的代碼來實作處理函數:
private static void TabHandler(objectsender, Autodesk.AutoCAD.ApplicationServices.TabbedDialogEventArgs e)
{
EmployeeOptionsEmployeeOptionsPage = new EmployeeOptions();
e.AddTab("AcmeEmployee Options",
new TabbedDialogExtension(
EmployeeOptionsPage,
new TabbedDialogAction(EmployeeOptionsPage.OnOk)));
}
我們首先執行個體化了一個EmployeeOptions對象。然後調用e.AddTab(),在這個函數中傳入了一個TabbedDialogExtension的執行個體。TabbedDialogExtension的構造函數使用了EmployeeOptionsPage執行個體和一個TabbedDialogAction對象。TabbedDialogAction對象的參數可以是Ok, Cancel 或Help回調函數。在這個函數中,我們使用的是OK。
22) 現在剩下的就是确定回調函數的内容,也就是ONOK的内容。前面已經說過了,我們隻要設定AsdkClass1的static成員,也就是設定tb_DivisionManager 和tb_EmployeeDivision編輯框中的值。下面是代碼:
public void OnOk()
{
AsdkClass1.sDivisionDefault= tb_EmployeeDivision.Text;
AsdkClass1.sDivisionManager= tb_DivisionManager.Text;
}
編譯、裝載并選擇AutoCAD的選項菜單項來看一下我們的自定義對話框。試試設定對話框中的值并執行個體化一個雇員。你可以使用PRINTOUTEMPLOYEE指令來檢視詳細資訊。
附加的問題:怎樣讓對話框的編輯框能自動顯示為AsdkClass1中的Manager和Division字元串的内容?
Autodesk官方最新的.NET教程(七)(C#版)
第7章 事件
本章将讨論AutoCAD中的事件。我們将介紹事件處理函數的使用,特别是監視AutoCAD指令的事件處理函數和監視被AutoCAD指令修改的對象的事件處理函數。在解釋怎樣在C#中實作AutoCAD的事件處理之前,我們将首先簡要地讨論一下.NET中的事件。
第一部分 C#中的事件
事件隻是用來通知一個行為已經發生的資訊。在ObjectARX中,我們使用反應器(reactor)來處理AutoCAD的事件。而在AutoCAD .NET API中,ObjectARX反應器被換成了事件。
事件處理函數(或者叫回調函數)是用來監視和回報程式中出現的事件。事件可以以不同的形式出現。
在介紹AutoCAD .NET API中的事件之前,讓我們先來簡單地了解一下代理。
第1a部分 代理
代理是一個存儲方法索引的類(概念與函數指針類似)。代理對方法是類型安全的(與C中的函數指針類似)。代理有特定的形式和傳回類型。代理可以封裝符合這種特定形式的任何方法。
代理的一個用途就是作為産生事件的類的分發器。事件是.NET環境中第一級别的對象。雖然C#把事件處理的許多細節給隐藏掉了,但事件總是由代理來實作的。事件代理可以多次調用(就是它們可以存儲多于1個的事件處理方法的索引)。它們儲存了用于事件的一個注冊事件處理的清單。一個典型的代理有以下的形式:
public delegate Event (Object sender, EventArgs e)
第一個參數sender表示引發事件的對象。第二個參數e是一個EventArgs參數(或者是一個派生的類),這個對象通常包含用于事件處理函數的資料。
第1b部分 +=和-=語句
要使用事件處理函數,我們必須把它與事件聯系起來。這要通過使用+=語句。+=和-=允許你在運作時連接配接、斷開或修改與事件聯系的處理函數。
當我們使用+=語句時,我們要确定事件引發者的名字,并要使用new語句來确定事件處理函數,例如:
MyClass1.AnEvent += new HandlerDelegate(EHandler)
前面我們說過要使用-=語句從事件處理函數中斷開事件(移除聯系)。文法如下所示:
MyClass1.AnEvent -= new HandlerDelegate(EHandler)
第2部分 處理.NET中的AutoCAD事件
在ObjectARX中,我們使用反應器來封裝AutoCAD事件。在AutoCAD .NET API中,我們可以使用事件來代替ObjectARX反應器。
通常,處理AutoCAD事件的步驟如下:
1. 建立事件處理函數
當一個事件發生時,事件處理函數(或稱為回調函數)被調用。任何我們想要處理的回應AutoCAD事件的動作都在事件處理函數中進行。
例如,假定我們隻想通知使用者一個AutoCAD對象已被加入。我們可以使用AutoCAD資料庫事件”ObjectAppended”來完成。我們可以編寫回調函數(事件處理函數)如下:
public void objAppended(object o, ObjectEventArgs e)
{
// 在這裡加入處理代碼
}
函數中的第一個參數代表AutoCAD資料庫。第二個參數代表ObjectEventArgs類,它可能包含對處理函數有用的資料。
2. 把事件處理函數與事件聯系起來
為了開始監視動作,我們必須把事件處理函數與事件聯系起來。在這裡,當一個對象加入到資料庫時,ObjectAppended事件将會發生。但是,事件處理函數不會響應這個事件,除非我們把它與這個事件聯系起來,例如:
Database db;
db = HostApplicationServices.WorkingDatabase;
db. ObjectAppended += new ObjectEventHandler(objAppended);
3. 斷開事件處理函數
要終止監視一個動作,我們必須斷開事件處理函數與事件的聯系。當對象被加入時,我們想要停止通知使用者這個事件,我們要斷開事件處理函數與事件ObjectAppended的聯系。
db. ObjectAppended -= new ObjectEventHandler(objAppended);
第3部分 使用事件處理函數來控制AutoCAD的行為
本章的目的是解釋AutoCAD事件怎樣才能被用于控制AutoCAD圖形中的行為。現在,讓我們使用前一章(第六章)的内容在AutoCAD圖形中建立幾個EMPLOYEE塊索引。我們不想讓使用者能改變EMPLOYEE塊索引的位置,而對于其它的非EMPLOYEE塊索引的位置則沒有這個限制。我們将混合使用資料庫與文檔事件來做到這一點。
首先,我們想要監視将要被執行的AutoCAD指令(使用CommandWillStart事件)。特别地,我們要監視MOVE指令。另外,當一個對象要被修改時,我們應該被通知(使用ObjectOpenedForModify事件),這樣我們可以确定它是否為一個EMPLOYEE塊索引。如果這時就修改對象可能是無效的,因為我們的修改可能會再次觸發事件,進而引起不穩定的行為。是以,我們要等待Move指令的執行結束(使用CommandEnded事件),這時就可以安全地修改對象了。當然,任何對塊索引的修改将會觸發ObjectOpenedForModify事件。我們還需要設定一些全局變量來表明一個MOVE指令在運作和被修改的對象是一個EMPLOYEE塊索引。
注意:因為本章需要比較多的代碼來獲得想要的結果,是以我們不會解釋任何與事件處理無關的代碼,而隻是将它們粘貼到事件處理函數中。這裡的重點是成功建立和注冊事件處理函數。
第一步:建立新工程
我們以第六章的工程開始。請新加入一個類AsdkClass2。我們還要加入四個全局變量。前兩個是Boolean型的:一個用來表示我們監視的指令是否是活動的,另外一個用來表示ObjectOpenedForModify事件處理函數是否該被忽略。
//全局變量
bool bEditCommand;
bool bDoRepositioning;
接下來,我們要聲明一個全局變量來表示一個ObjectIdCollection,它用來存儲我們所選擇的要修改的對象的ObjectID。
ObjectIdCollection changedObjects = new ObjectIdCollection();
最後,我們要聲明一個全局變量來表示一個Point3dCollection,它用來包含我們所選對象的位置(三維點)。
Point3dCollection employeePositions = new Point3dCollection();
第2步:建立第一個文檔事件處理函數(回調函數)
現在我們要建立一個事件處理函數。當AutoCAD指令開始執行的時候它會通知我們。我們要檢查GlobalCommandName的值是否為MOVE。
if ( e.GlobalCommandName == "MOVE" )
{
}
如果MOVE指令開始執行的話,我們要相應地設定Boolean變量bEditCommand的值,這樣我們可以知道我們所監視的指令是活動的。同樣地,我們應該把另外一個Boolean變量bDoRepositioning設定為false來忽略ObjectOpenedForModify事件處理函數。兩個變量設定好以後,在指令活動期間,我們必須要獲得所選塊索引的資訊。
我們還應該把兩個集合對象的内容清空。我們隻關心目前選擇的對象。
第3步: 建立資料庫事件處理函數(回調函數)
無論什麼時候一個對象被打開并要被修改時,資料庫事件處理函數會被調用。當然,如果這時我們監視的指令不是活動的,我們就應該跳過任何被這個回調函數調用的内容。
if ( bEditCommand == false )
{
return;
}
同樣地,如果我們監視的指令已經結束,而ObjectOpenedForModify事件被另一個回調函數再次觸發的話,而這時有對象被修改時,我們要阻止所有由這個回調函數執行的動作。
if ( bDoRepositioning == true )
{
return;
}
這個回調函數剩餘部分的代碼用來驗證我們是否正在處理EMPLOYEE塊索引。如果是的話,我們就擷取它的ObjectID和位置(三維點)。下面的代碼可以被粘貼到這個事件處理函數函數。
public void objOpenedForMod(object o, ObjectEventArgs e)
{
if ( bEditCommand == false )
{
return;
}
if ( bDoRepositioning == true )
{
return;
}
ObjectId objId;
objId = e.DBObject.ObjectId;
Transaction trans;
Database db;
db = HostApplicationServices.WorkingDatabase;
trans = db.TransactionManager.StartTransaction();
using(Entity ent = (Entity)trans.GetObject(objId, OpenMode.ForRead, false))
{
if ( ent.GetType().FullName.Equals( "Autodesk.AutoCAD.DatabaseServices.BlockReference" ) )
{ //We use .NET//s RTTI to establish type.
BlockReference br = (BlockReference)ent;
//Test whether it is an employee block
//open its extension dictionary
if ( br.ExtensionDictionary.IsValid )
{
using(DBDictionary brExtDict = (DBDictionary)trans.GetObject(br.ExtensionDictionary, OpenMode.ForRead))
{
if ( brExtDict.GetAt("EmployeeData").IsValid )
{
//successfully got "EmployeeData" so br is employee block ref
//Store the objectID and the position
changedObjects.Add(objId);
employeePositions.Add(br.Position);
//Get the attribute references,if any
AttributeCollection atts;
atts = br.AttributeCollection;
if ( atts.Count > 0 )
{
foreach(ObjectId attId in atts )
{
AttributeReference att;
using(att = (AttributeReference)trans.GetObject(attId, OpenMode.ForRead, false))
{
changedObjects.Add(attId);
employeePositions.Add(att.Position);
}
}
}
}
}
}
}
}
trans.Commit();
}
第4步 建立第二個文檔事件處理函數(回調函數)
當一個指令結束時,第三個事件處理函數被調用。同樣地,我們要檢查全局變量來驗證這個将要結束的指令是我們監視的指令。如果是我們監視的,那麼我們要重置這個變量:
if ( bEditCommand == false )
{
return;
}
bEditCommand = false;
這個回調函數執行的動作将會再次觸發ObjectOpenedForModify事件。我們必須确定在這個回調函數中跳過了所有與此事件有關的動作。
//設定标志來跳過OpenedForModify處理函數
bDoRepositioning = true;
這個回調函數的剩餘代碼用來把EMPLOYEE塊索引和它的關聯屬性引用的目前(修改過的)位置與它們的初始位置作比較。如果位置改變了,我們在這個回調函數中把它們重置這初始的位置。下面的代碼可以被粘貼到這個事件處理函數中。
public void cmdEnded(object o , CommandEventArgs e)
{
//Was our monitored command active?
if ( bEditCommand == false )
{
return;
}
bEditCommand = false;
//Set flag to bypass OpenedForModify handler
bDoRepositioning = true;
Database db = HostApplicationServices.WorkingDatabase;
Transaction trans ;
BlockTable bt;
Point3d oldpos;
Point3d newpos;
int i ;
for ( i = 0; i< changedObjects.Count; i++)
{
trans = db.TransactionManager.StartTransaction();
using( bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead) )
{
using(Entity ent = (Entity)trans.GetObject(changedObjects[i], OpenMode.ForWrite))
{
if ( ent.GetType().FullName.Equals("Autodesk.AutoCAD.DatabaseServices.BlockReference") )
{ //We use .NET//s RTTI to establish type.
BlockReference br = (BlockReference)ent;
newpos = br.Position;
oldpos = employeePositions[i];
//Reset blockref position
if ( !oldpos.Equals(newpos) )
{
using( trans.GetObject(br.ObjectId, OpenMode.ForWrite) )
{
br.Position = oldpos;
}
}
}
else if ( ent.GetType().FullName.Equals("Autodesk.AutoCAD.DatabaseServices.AttributeReference") )
{
AttributeReference att = (AttributeReference)ent;
newpos = att.Position;
oldpos = employeePositions[i];
//Reset attref position
if ( !oldpos.Equals(newpos) )
{
using( trans.GetObject(att.ObjectId, OpenMode.ForWrite))
{
att.Position = oldpos;
}
}
}
}
}
trans.Commit();
}
}
第5步 建立指令來注冊/斷開事件處理函數
建立一個ADDEVENTS指令,使用+=語句來把上面的3個事件處理函數連接配接到各自的事件。在這個指令中,我們還應該設定全局Boolean變量:
bEditCommand = false;
bDoRepositioning = false;
建立另外一個指令REMOVEEVENTS,使用-=語句把事件處理函數與事件斷開。
第6步: 測試工程
要測試這個工程,請使用CREATE指令建立一個或多個EMPLOYEE塊索引。如果你要作比較的話,你也可以插入一些非EMPLOYEE的塊索引。
在指令行中鍵入ADDEVENTS指令來執行它。
在指令行中輸入MOVE指令,然後選擇你想要的塊索引。注意,當MOVE指令結束時,EMPLOYEE塊索引(包括屬性)還留在原處。
執行REMOVEEVENTS指令,然後在試一下MOVE指令。注意,EMPLOYEE塊索引現在可以被移動了。
附加的問題:添加一個附加的回調函數,當使用者改變EMPLOYEE塊索引的”Name”屬性時,這個回調函數被觸發。