Autodesk官方最新的.NET教程(七)(vb.net版)
2024-07-10 13:00:38
供稿:網友
第7章 事件
本章將討論autocad中的事件。我們將介紹事件處理函數的使用,特別是監視autocad命令的事件處理函數和監視被autocad命令修改的對象的事件處理函數。在解釋怎樣實現autocad的事件處理之前,我們將首先簡要地討論一下.net中的事件。
第一部分 vb.net中的事件
事件只是用來通知一個行為已經發生的信息。在objectarx中,我們使用反應器(reactor)來處理autocad的事件。而在autocad .net api中,objectarx反應器被換成了事件。
事件處理函數(或者叫回調函數)是用來監視和反饋程序中出現的事件。事件可以以不同的形式出現。
在介紹autocad .net api中的事件之前,讓我們先來簡單地了解一下代理。
第1a部分 代理
代理是一個存儲方法索引的類(概念與函數指針類似)。代理對方法是類型安全的(與c中的函數指針類似)。代理有特定的形式和返回類型。代理可以封裝符合這種特定形式的任何方法。
代理的一個用途就是作為產生事件的類的分發器。事件是.net環境中第一級別的對象。雖然vb.net把事件處理的許多細節給隱藏掉了,但事件總是由代理來實現的。事件代理可以多次調用(就是它們可以存儲多于1個的事件處理方法的索引)。它們保存了用于事件的一個注冊事件處理的列表。一個典型的代理有以下的形式:
public delegate event (sender as object, e as eventargs)
第一個參數sender表示引發事件的對象。第二個參數e是一個eventargs參數(或者是一個派生的類),這個對象通常包含用于事件處理函數的數據。
第1b部分 addhandler和removehandler語句
要使用事件處理函數,我們必須把它與事件聯系起來。這要通過使用addhandler語句。addhandler和removehandler允許你在運行時連接、斷開或修改與事件聯系的處理函數。
當我們使用addhandler語句時,我們要確定事件引發者的名字,并要使用addressof語句來確定事件處理函數,例如:
addhandler myclass1.anevent, addressof ehandler
前面我們說過要使用removehandler語句從事件處理函數中斷開事件(移除聯系)。語法如下所示:
removehandler myclass1.anevent, addressof ehandler
第2部分 處理.net中的autocad事件
在objectarx中,我們使用反應器來封裝autocad事件。在autocad .net api中,我們可以使用事件來代替objectarx反應器。
通常,處理autocad事件的步驟如下:
1. 創建事件處理函數
當一個事件發生時,事件處理函數(或稱為回調函數)被調用。任何我們想要處理的回應autocad事件的動作都在事件處理函數中進行。
例如,假定我們只想通知用戶一個autocad對象已被加入。我們可以使用autocad數據庫事件”objectappended”來完成。我們可以編寫回調函數(事件處理函數)如下:
sub objappended(byval o as object, byval e as objecteventargs)
messagebox.show("objectappended!")
‘在這里加入一些代碼
end sub
函數中的第一個參數代表autocad數據庫。第二個參數代表objecteventargs類,它可能包含對處理函數有用的數據。
2. 把事件處理函數與事件聯系起來
為了開始監視動作,我們必須把事件處理函數與事件聯系起來。在這里,當一個對象加入到數據庫時,objectappended事件將會發生。但是,事件處理函數不會響應這個事件,除非我們把它與這個事件聯系起來,例如:
dim db as database
db = hostapplicationservices.workingdatabase()
addhandler db.objectappended, new objecteventhandler(addressof objappended)
3. 斷開事件處理函數
要終止監視一個動作,我們必須斷開事件處理函數與事件的聯系。當對象被加入時,我們想要停止通知用戶這個事件,我們要斷開事件處理函數與事件objectappended的聯系。
removehandler db.objectappended, addressof objappended
第3部分 使用事件處理函數來控制autocad的行為
本章的目的是解釋autocad事件怎樣才能被用于控制autocad圖形中的行為。現在,讓我們使用前一章(第六章)的內容在autocad圖形中創建幾個employee塊索引。我們不想讓用戶能改變employee塊索引的位置,而對于其它的非employee塊索引的位置則沒有這個限制。我們將混合使用數據庫與文檔事件來做到這一點。
首先,我們想要監視將要被執行的autocad命令(使用commandwillstart事件)。特別地,我們要監視move命令。另外,當一個對象要被修改時,我們應該被通知(使用objectopenedformodify事件),這樣我們可以確定它是否為一個employee塊索引。如果這時就修改對象可能是無效的,因為我們的修改可能會再次觸發事件,從而引起不穩定的行為。所以,我們要等待move命令的執行結束(使用commandended事件),這時就可以安全地修改對象了。當然,任何對塊索引的修改將會觸發objectopenedformodify事件。我們還需要設置一些全局變量來表明一個move命令在運行和被修改的對象是一個employee塊索引。
注意:因為本章需要比較多的代碼來獲得想要的結果,所以我們不會解釋任何與事件處理無關的代碼,而只是將它們粘貼到事件處理函數中。這里的重點是成功創建和注冊事件處理函數。
第一步:創建新工程
我們以第六章的工程開始。請新加入一個類asdkclass2。我們還要加入四個全局變量。前兩個是boolean型的:一個用來表示我們監視的命令是否是活動的,另外一個用來表示objectopenedformodify事件處理函數是否該被忽略。
'全局變量
dim beditcommand as boolean
dim bdorepositioning as boolean
接下來,我們要聲明一個全局變量來表示一個objectidcollection,它用來存儲我們所選擇的要修改的對象的objectid。
dim changedobjects as new objectidcollection()
最后,我們要聲明一個全局變量來表示一個point3dcollection,它用來包含我們所選對象的位置(三維點)。
dim employeepositions as new point3dcollection()
第2步:創建第一個文檔事件處理函數(回調函數)
現在我們要創建一個事件處理函數。當autocad命令開始執行的時候它會通知我們。我們要檢查globalcommandname的值是否為move。
if e.globalcommandname = "move" then
'set the global variables
‘
‘
‘'delete all stored information
‘
‘
end if
如果move命令開始執行的話,我們要相應地設置boolean變量beditcommand的值,這樣我們可以知道我們所監視的命令是活動的。同樣地,我們應該把另外一個boolean變量bdorepositioning設置為false來忽略objectopenedformodify事件處理函數。兩個變量設置好以后,在命令活動期間,我們必須要獲得所選塊索引的信息。
我們還應該把兩個集合對象的內容清空。我們只關心當前選擇的對象。
第3步: 創建數據庫事件處理函數(回調函數)
無論什么時候一個對象被打開并要被修改時,數據庫事件處理函數會被調用。當然,如果這時我們監視的命令不是活動的,我們就應該跳過任何被這個回調函數調用的內容。
if beditcommand = false then
return
end if
同樣地,如果我們監視的命令已經結束,而objectopenedformodify事件被另一個回調函數再次觸發的話,而這時有對象被修改時,我們要阻止所有由這個回調函數執行的動作。
if bdorepositioning = true then
return
end if
這個回調函數剩余部分的代碼用來驗證我們是否正在處理employee塊索引。如果是的話,我們就獲取它的objectid和位置(三維點)。下面的代碼可以被粘貼到這個事件處理函數函數。
public sub objopenedformod(byval o as object, byval e as objecteventargs)
if beditcommand = false then
return
end if
if bdorepositioning = true then
return
end if
dim objid as objectid
objid = e.dbobject.objectid
dim trans as transaction
dim bt as blocktable
dim db as database
db = hostapplicationservices.workingdatabase
trans = db.transactionmanager.starttransaction()
try
'use it to open the current object!
dim ent as entity = trans.getobject(objid, openmode.forread, false)
if typeof ent is blockreference then 'we use .net's rtti to establish type.
dim br as blockreference = ctype(ent, blockreference)
'test whether it is an employee block
'open its extension dictionary
if br.extensiondictionary().isvalid then
dim brextdict as dbdictionary = trans.getobject(br.extensiondictionary(), openmode.forread)
if brextdict.getat("employeedata").isvalid then
'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
dim atts as attributecollection
atts = br.attributecollection
if atts.count > 0 then
dim attid as objectid
for each attid in atts
dim att as attributereference
att = trans.getobject(attid, openmode.forread, false)
changedobjects.add(attid)
employeepositions.add(att.position)
next
end if
end if
end if
end if
trans.commit()
finally
trans.dispose()
end try
end sub
第4步 創建第二個文檔事件處理函數(回調函數)
當一個命令結束時,第三個事件處理函數被調用。同樣地,我們要檢查全局變量來驗證這個將要結束的命令是我們監視的命令。如果是我們監視的,那么我們要重置這個變量:
if beditcommand = false then
return
end if
beditcommand = false
這個回調函數執行的動作將會再次觸發objectopenedformodify事件。我們必須確定在這個回調函數中跳過了所有與此事件有關的動作。
'設置標志來跳過openedformodify處理函數
bdorepositioning = true
這個回調函數的剩余代碼用來把employee塊索引和它的關聯屬性引用的當前(修改過的)位置與它們的初始位置作比較。如果位置改變了,我們在這個回調函數中把它們重置這初始的位置。下面的代碼可以被粘貼到這個事件處理函數中。
public sub cmdended(byval o as object, byval e as commandeventargs)
'was our monitored command active?
if beditcommand = false then
return
end if
beditcommand = false
'set flag to bypass objectopenedformodify handler
bdorepositioning = true
dim db as database = hostapplicationservices.workingdatabase
dim trans as transaction
dim bt as blocktable
dim oldpos as point3d
dim newpos as point3d
dim i as integer
dim j as integer = 1
for i = 0 to changedobjects.count - 1
trans = db.transactionmanager.starttransaction()
try
bt = trans.getobject(db.blocktableid, openmode.forread)
dim ent as entity = ctype(trans.getobject(changedobjects.item(i), openmode.forwrite), entity)
if typeof ent is blockreference then 'we use .net's rtti to establish type.
dim br as blockreference = ctype(ent, blockreference)
newpos = br.position
oldpos = employeepositions.item(i)
'reset blockref position
if not oldpos.equals(newpos) then
trans.getobject(br.objectid, openmode.forwrite)
br.position = oldpos
end if
elseif typeof ent is attributereference then
dim att as attributereference = ctype(ent, attributereference)
newpos = att.position
oldpos = employeepositions.item(i)
'reset attref position
if not oldpos.equals(newpos) then
trans.getobject(att.objectid, openmode.forwrite)
att.position = oldpos
end if
end if
bt.dispose()
trans.commit()
finally
trans.dispose()
end try
next
end sub
第5步 創建命令來注冊/斷開事件處理函數
創建一個addevents命令,使用+=語句來把上面的3個事件處理函數連接到各自的事件。在這個命令中,我們還應該設置全局boolean變量:
beditcommand = false
bdorepositioning = false
創建另外一個命令removeevents,使用removehandler語句把事件處理函數與事件斷開。
第6步: 測試工程
要測試這個工程,請使用create命令創建一個或多個employee塊索引。如果你要作比較的話,你也可以插入一些非employee的塊索引。
在命令行中鍵入addevents命令來執行它。
在命令行中輸入move命令,然后選擇你想要的塊索引。注意,當move命令結束時,employee塊索引(包括屬性)還留在原處。
執行removeevents命令,然后在試一下move命令。注意,employee塊索引現在可以被移動了。
附加的問題:添加一個附加的回調函數,當用戶改變employee塊索引的”name”屬性時,這個回調函數被觸發。