探索VB系列中的事件處理的奧秘
事件是你的代碼兵器庫中的主要部分,無論你用Visual Basic? 6.0,Visual Basic .NET 2002,Visual Basic .NET 2003,還是Visual Basic 2005。窗體和控件引發事件,同時你的代碼處理這些事件。你用Visual Basic寫的最初應用程序大多會是在一個窗體上放置一個
事件是你的代碼兵器庫中的主要部分,無論你用Visual Basic? 6.0,Visual Basic
.NET 2002,Visual Basic
.NET 2003,還是Visual Basic 2005。窗體和控件引發事件,同時你的代碼處理這些事件。你用Visual Basic寫的最初應用程序大多會是在一個窗體上放置一個按鈕,處理這個click事件,并在運行時你點擊這個按鈕會顯示某些文本在提示框中。還有什么比這更容易?
但是你又真正了解事件多少呢?在你向某個類中添加一個事件處理程序是將會發生什么?在本文中,基于我為 AppDev 所寫的課件,我將用各種方法來演示事件和事件處理程序交互,并且我將說明它們如何能解決一般問題。也許這些信息中的一些對你來說并不新鮮,但是如果你對事件的了解并不深入,這里肯定有些東西讓你驚奇。在任一情況下,
下載這兩個示例應用程序(一個是用Visual Basic .NET .2002和2003,一個是用Visual Basic 2005)并理解之。所有內容適用于Visual Basic .NET2002 和 2003 及 Visual Basic 2005,除了最后的論及自定義事件的部分,它只能在Visual Basic 2005下工作。
我將假定你已有一些關于委托和多路廣播委托的基本
知識。如果你沒有研究過這些重要的Microsoft.NET Framework特性,現在你就該去做了。獲得這些問題的更多信息可以看看Ted Pattison的兩部分關于委托的概論。
事件
Visual Basic(在Visual Basic .NET之前)為你提供了創建和處理事件的簡單機制,并且Visual Basic .NET 2002和2003提供了幾個不同的方法來做它們。Visual Basic 2005甚至允許你更強地控制事件處理程序,正如你將在本文里所看到的。
事件提供一個松散的聯系機制,它允許類為在將來某個時間可能或也許不可能發生的通知注冊。如果“偵聽器”得到它們正在等待的事件發生的通知,它們就處理這種情況。如果不,它們只是保持監聽。一個按鈕點擊事件處理程序用類提供的按鈕的功能(functionality)來注冊它自己;在一個用戶點擊這個按鈕時,這個按鈕的類引發Click事件,所有偵聽器(這里可能是此按鈕的多個Click事件處理程序)運行它們的代碼,并繼續執行代碼。
我的示例程序包括一組類(FileSearch1到FileSearch5用于Visual Basic .NET 2002 和 2003,而FileSearch1到FileSearch6可用于Visual Basic 2005)在一個指定位置搜索文件并在找到時引發一個事件。FileSearch類只想在某個令人感興趣的事情發生時讓某個對應的類監聽到。在這種情況下,每當FileSearch類發現另一個文件時某個有趣的事情就會發生。許多偵聽器類可能會希望對該事件做出反應。
以.NET的觀點來說,一個類可以在代碼執行的任何一點引發一個事件。其他類可以訂閱這個事件,并且它們可以在事件發生時通過.NET Framework獲得通知。這個引發事件的類一般并不會知道有多少(如果有的話)偵聽器,盡管它可能做出某些努力以收集這個信息,正如你將在本文后面所看到的。另外,多偵聽器可以注冊以獲得通知,并且每個都可以被通知而對其他任何偵聽器一無所知。
以Visual Basic 6.0方法處理
.NET Framework和Visual Basic .NET語言的設計者,已做了非常充分的工作以確保你可以在.NET使用事件如同你在Visual Basic 6.0中所做的一樣。這就是你可以:
* 使用Event關鍵詞聲明一個事件
* 使用RaiseEvent語句引發一個事件
* 使用一個WithEvents變量處理事件
與.NET的實質上的不同之處是底層的機制。對應于在Visual Basic 6.0中使用某些隱藏 plumbing,Visual Basic .NET使用一個可見的、擴展的、公共plumbing-委托-來管理事件處理。
Visual Basic 6.0 和Visual Basic .NET版本之間的另一個不同是在.NET中:你可以使用Handles子句指示在對一個特殊事件的響應中應該運行的一個特殊過程。Handles子句允許任何與事件的參數簽名相符的過程來響應這個事件。聽起來很像一個委托,而在表象之下,它就是委托。在編譯時間,.NET Framework用你的事件名稱創建一個委托類,只是在結尾添加“EventHandler”字樣。舉個例子,在你聲明一個命名為FileFound的事件時,.NET Framework創建為你創建一個命名為FileFoundEventHandler的委托類型。處理這個事件的每個過程必須有一個符合委托類型的簽名。
點擊在示例窗體上的RaiseEvent按鈕演示了Visual Basic .NET如何支持以Visual Basic 6.0為基礎的事件處理。示例項目包括了FileSearch1類,它使用以下代碼建立事件:
clearcase/" target="_blank" >cc66" width="90%" align="center" bgcolor="#dadacf" border="1">
’’ 以下代碼來自FileSearch1.vb
Public Class FileSearch1
’’在指定位置搜索文件
’’一旦找到,這個類就為每個找到的文件引發FileFound 事件
Public Event FileFound(ByVal fi As FileInfo) ... ’’ 這里的代碼去掉了...
End Class |
在它找到文件時,FileSearch1類引發FileFound事件:
’’以下代碼來自FileSearch1.vb
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)
For Each fi As FileInfo In afi
RaiseEvent FileFound(fi)
Next
alFiles.AddRange(afi) |
在frmMain.vb,你將找到以下聲明,它允許代碼使用變量fs1來對由FileSearch1實例引發的事件做出反應:
Private WithEvents fs1 As FileSearch1 |
點擊RaiseEvent運行以下代碼:
’’以下代碼來自FileSearch1.vb
fs1 = New FileSearch1( _
Me.txtSearchPath.Text, Me.txtFileSpec.Text, _
Me.chkSearchSubfolders.Checked)Try
fs1.Execute()
Catch
End Try |
最后,frmMain.vb包括了一個事件處理程序,它處理FileSearch1.FileFound事件:
’’以下代碼來自FileSearch1.vbPrivate Sub EventHandler1( _
ByVal NewFile As System.IO.FileInfo) _
Handles fs1.FileFound AddText("EventHandler1: " & NewFile.FullName)
End Sub |
盡管frmMain.vb只包括處理FileSearch1.FileFound事件的一個單過程,你將會有不止一個過程處理一個特定事件是相當可能的(并且是很可能的)。這就是說,當FileSearch1類引發它的FileFound事件,多個過程可能處理它是可能的。這聽起來應該很像多路廣播委托的概念,因為它就是多路廣播委托。本質上,.NET將事件轉換為委托類。用ILDASM.exe研究一下IL(中間代碼)就會撥云見日了。
用ILDASM研究事件
如果你使用ILDASM(包含在.NET Framework SDK中的.NET Framework IL反匯編工具)來打開這個示例的可執行版本,你將獲得如你在圖 1中所看到的信息。盡管FileSearch1并沒有顯式地包含一個命名為FileFoundEventHandler的多路廣播委托,Visual Basic .NET編譯器已創建了一個(多路廣播委托),對應于代碼聲明的由FileFound事件定義的類型。編譯器也創建了一個表示事件偵聽器的委托類型FileFoundEventHandler的名為FileFoundEvent的實例。
圖 1 在ILDASM中的示例代碼
|
使用這個技術,編譯器可以強迫嚴格遵循你的事件聲明的參數簽名,而不用你再去為創建你自己的委托類型和在你的事件聲明中預定的類型而操心。正如你后面將看到的一樣,你可以完全隨意地創建你自己的委托表示你的事件并且可用這個委托作為你的事件的類型。
多事件處理程序
我將討論的下一個類:FileSearch2,它是FileSearch1類的一個相似的拷貝。使用FileSearch2中的唯一的不同是:示例窗體對于FileSearch2類的 FileFound事件包括多個偵聽器。這就是,frmMain.vb包括以下的聲明:
’’以下代碼來自FileSearch1.vb
Private WithEvents fs2 As FileSearch2 |
這個示例窗體也包括了在圖 2 中所示的事件處理程序。這些事件處理程序也監聽由FileSearch3和FileSearch4類引發的FileFound事件,這個我下面還有講到。
’ From frmMain.vb
Private Sub EventHandler2( _
ByVal NewFile As System.IO.FileInfo) _
Handles fs2.FileFound, fs3.FileFound, fs4.FileFound
AddText("EventHandler2: " & NewFile.FullName)
End Sub
Private Sub EventHandler3( _
ByVal NewFile As System.IO.FileInfo) _
Handles fs2.FileFound, fs3.FileFound, fs4.FileFound
AddText("EventHandler3: " & NewFile.FullName)
End Sub |
點擊在主窗體上的Multi-Listener按鈕會創建一個FileSearch2類的實例,調用這個實例的執行(execute)方法,就會顯示如圖 3中所示的輸出。
圖 3 多偵聽器(Multiple Listeners)允許多過程(Multiple Procedures)運行
|
但是注意,當你使用多個Handles子句對同一事件反應時,你完全不能控制事件處理程序運行的順序。.NET Framework提供兩個選擇,這將稍后在文章中討論,它允許你獲得對多偵聽器的更強地控制。 異常和多個事件處理程序(Multiple Event Handlers)
正如你已經看到的,一切都讓人稱心如意。如果你有一個事件的多個處理程序,當事件引發時,.NET Framework將依次調用每個處理程序。到目前為止一切順利。如果其中某個事件處理程序產生一個異常時將會發生什么?事情就沒有那么順利了。
為證實這個問題,點擊在示例窗體上的RaiseEvent Error按鈕。這個例子創建了FileSearch3類的一個新的實例(在這個類本身沒有什么新東西)。在圖 4 中的示例窗體中提供了若干過程,它處理了FileSearch3.FileFound事件,但是有個過程拋出了一個異常。

圖 5 某個事件偵聽器拋出一個錯誤
Figure 4 One Event Handler Raises an Error
’ From frmMain.vb
Private Sub EventHandler2( _
ByVal NewFile As System.IO.FileInfo) _
Handles fs2.FileFound, fs3.FileFound, fs4.FileFound
AddText("EventHandler2: " & NewFile.FullName)
End Sub
Private Sub EventHandler3( _
ByVal NewFile As System.IO.FileInfo) _
Handles fs2.FileFound, fs3.FileFound, fs4.FileFound
AddText("EventHandler3: " & NewFile.FullName)
End Sub
Private Sub EventHandler4( _
ByVal NewFile As System.IO.FileInfo) _
Handles fs3.FileFound, fs4.FileFound
AddText("EventHandler4: Throwing exception!")
Throw New ArgumentException
End Sub
|
在你運行這個代碼時會發生什么?你將獲得圖 5所示的結果。若有任何一個事件偵聽器引發一個異常,整個“事件處理鏈(event-handling chain)”就停下來。如果你停下來考慮這是怎么回事,你會知道這個行為是有意義的。
研究異常(Exception)行為
我將很快給你看看從FileSearch3類中選取的代碼。每次一個類的實例找到一個文件,實例就引發它的FileFound事件。這一效果導致.NET Framework相繼地運行每個事件處理過程??纯聪旅娴拇a:
’’以下代碼來自FileSearch3.vb
’’搜索符合的文件名
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)For Each fi As FileInfo In afi
’’這相當于:
’’ FileFoundEventHandler.Invoke(fi)
RaiseEvent FileFound(fi)
Next
alFiles.AddRange(afi) |
這個技術的問題(正如你已經看到的)是:如果一個異常出現在任何一個偵聽器中,異?;貪L(bubbles back)到事件引發(event-raising)代碼,.NET Framework不再調用事件偵聽器,而事件處理就慢慢停了下來。
如果查看一下IL為示例代碼所生成的,情況就變得清楚了。圖 6 顯示了在ILDASM中FileSearch3.Search方法的反匯編。你在類中看到的RaiseEvent語句直到一個FileFoundEventHandler.Invoke方法調用后才會編譯。在內部,一旦這個方法已經調用,你的代碼將控制權交給委托的執行過程,同時如果一個未處理的異常在調用列表的任何地方發生,這個異?;貪L到調用者(這個代碼),同時再沒有偵聽器獲得調用。
這里有一個方案。勝于一再地簡單調用RaiseEvent語句,它可能為你而顯式地調用每個單獨的偵聽器。你可以利用Delegate類的成員以解決在多偵聽器下的未處理異常問題。
手工調用每個偵聽器
盡管RaiseEvent機制是方便的(并且也是令人覺得舒服的,如果你使用Visual Basic 6.0的話)它也有它的缺點,正如你所已經看到的。比依賴于RaiseEvent調用事件偵聽器更好的是:你可以自己做這個工作。而你獲得了一定的靈活性是你放棄使用RaiseEvent時的舒適為代價的。
如果你想要完全控制事件偵聽器的調用而不只是希望事情如你所愿的方法解決,你將需要利用隱式的FileFoundEventHandler委托類型。你通過調用事件委托實例本身的GetInvocationList方法重新獲得一個包含所有事件的偵聽器的數組。一旦你有這個列表,你就可以獨立地調用每個偵聽器的Invoke方法,并且捕捉由事件處理程序引發的任何異常。如果任何偵聽器引發一個異常,你就能處理它并將程序繼續下去。
FileSearch4類包含其Search方法中的代碼如圖 7 顯示。通過點擊示例窗體上的GetInvocationList按鈕運行這個代碼。正如你將看到的,示例仍然調用引發一個錯誤的事件偵聽器,但是既然這樣,代碼就不會在第一次偵聽器引發錯誤時停止搜索文件。因為FileSearch4.Search方法包括獨立地調用每個偵聽器的代碼,它也可以為每個調用處理異常。
’ From FileSearch4.vb
Dim ListenerList() As System.Delegate
Dim Listener As FileFoundEventHandler
ListenerList = FileFoundEvent.GetInvocationList()
’ Search for matching file names.
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)
For Each fi As FileInfo In afi
For Each Listener In ListenerList
Try
Listener.Invoke(fi)
Catch
’ Something goes wrong? Just move on to
’ the next event handler.
End Try
Next
alFiles.AddRange(afi)
Next
|
FileSearch4.Search中的新代碼采取了以下動作:
* 聲明一個System.Delegate類型的數組以使得代碼可以追蹤到事件的所有偵聽器:
Dim ListenerList() As System.Delegate |
* 聲明一個描述事件的委托類型的實例,以遍歷偵聽器數組(你應該記得:所有偵聽器過程必須有其特定的類型,否則這個代碼將不能被編譯,這就是委托的工作方法):
Dim Listener As FileFoundEventHandler |
* 重新獲得偵聽器列表,調用FileFoundEvent委托的內部GetInvocationList方法:
ListenerList = FileFoundEvent.GetInvocationList() |
* 找到文件并遍歷包含cor-responding FileInfos的數組,正如你前面已經看到的一樣:
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)
For Each fi As FileInfo In afi
’’ Code removed here...
Next |
* 每找到一個文件,FileSearch4.Search就遍歷事件偵聽器的列表并獨立地調用每個委托的回調(Invoke)方法。這允許代碼捕捉(而且既然這樣,就忽略)任何由每個獨立地的偵聽器引發的異常。
For Each Listener In ListenerList
Try
Listener.Invoke(fi)
Catch
’’ 出了什么錯?只是前進到下一個事件處理程序
End Try
Next |
這個示例項目既沒有聲明FileFoundEventHandler類型也沒有聲明FileFoundEvent變量。Visual Basic .NET編譯器在你的代碼中發現事件聲明時它會創建這些條目。盡管你可以通過自己聲明這些對象以去處含糊不清的感覺,但你并不需要這樣做,因為Visual Basic .NET編譯器將會為你做好這個工作。
在Visual Basic .NET 2002 and 2003里你不能修改這個基于.NET的應用程序引發它們自己的內部事件的方式。(你可以在Visual Basic 2005中修改這個行為,正如你后面將要看到的)使用先于Visual Basic 2005的版本時,這里有個方法可以確保你不會在你的事件偵聽器中引發問題(記?。涸谌魏问录幚沓绦蛑幸粋€未處理的異常將導致.NET Framework停止為當前事件調用偵聽器)。為了這樣做,確保你自己的事件處理不允許回滾。如果你希望通過多個事件過程獲得一個單獨的事件處理的話,在你的事件過程中處理所有異常以使得你不會打破事件處理程序的鏈條。
使用.NET事件設計模式
雖然如你在Visual Basic 6.0和我前面的例子中所可能做的一樣引發事件并沒有什么錯誤,.NET Framework已經為事件采用了一種特別的設計模式,一種你應該在你的應用程序中采用的設計模式。在這個模式里,所有事件提供兩個參數:一個對象,提供一個對引發事件(一般命名為sender)對象的引用,和一個EventArgs對象(或者一個繼承于EventArgs的對象),提供相關信息給事件(一般命名為e)。
標準.NET Framework事件的設計模式添加了三個建議。首先,如果你的事件需要傳遞任何信息到它的偵聽器,你應該創建一個繼承于EventArgs的類并且它包含附加信息。你可以使用你的類的構造器來接受并存儲信息。在示例項目中,FileFoundEventArgs類如圖 8 所示。
第二,提供一個引發事件的過程。大多.NET Framework類從一個重載的protected過程引發事件,一般命名為OnEventName(在FileFound事件的情況下,過程會被命名為OnFileFound)。需要引發事件的代碼調用OnEventName過程,它將接著引發事件。使之成為一個protected方法意味著它可為當前類型的對象所用和基于繼承自當前類的任何對象所用。使之重載意味著繼承類可以改變事件的行為: 一個繼承類可以添加運行于調用基類的OnEventName過程之前或之后的代碼,或者可以全部跳過它們。在這個示例項目中,FileSearch5類提供以下protected過程:
’ From FileFoundEventsArgs.vb
Public Class FileFoundEventArgs
Inherits EventArgs
Private mfi As FileInfo
Public ReadOnly Property FileFound() As FileInfo
Get
Return mfi
End Get
End Property
Public Sub New(ByVal fi As FileInfo)
’ Store the FileInfo object for later use.
mfi = fi
End Sub
End Class |
’’ 來自 FileSearch9.vb
Protected Overridable Sub OnFileFound(ByVal fi As FileInfo)
RaiseEvent FileFound(Me, New FileFoundEventArgs(fi))
End Sub |
這個過程用RaiseEvent語句的第一個參數傳遞關鍵詞Me。這個關鍵詞引用在當前運行的代碼中的對象,它當然就是那個引發事件的對象。
第三,你可能發現創建你自己的事件委托是很有用的。盡管你無須定義一個顯式事件委托就可獲得事件委托,但在自己創建時可獲得一些靈活性。在你創建一個委托時,你正在為過程定義一個“類型”。如果你有不止一個事件,它們需要同一套參數,創建一個定義這個類型的委托將會有用。如果你需要修改這些參數,你可以只要修改委托,而不用修改事件聲明。
舉個例子,你可以聲明這個FileFound事件,不用事件委托,如下:
Public Event FileFound( _
ByVal sender As Object, ByVal e As FileFoundEventArgs) |
如果你這時想要聲明其他事件,使用相同參數,你將必須重復整個聲明:
Public Event FileFoundSomeOtherEvent( _
ByVal sender As Object, ByVal e As FileFoundEventArgs) |
作為選擇,你也可以聲明一個新的委托類型,它描述了你的事件的參數簽名:
Public Delegate Sub FileFoundEventHandler( _
ByVal sender As Object, ByVal e As FileFoundEventArgs) |
這時你應該聲明這個類型的聲明事件:
Public Event FileFound As FileFoundEventHandler
Public Event FileFoundSomeOtherEvent As FileFoundEventHandler |
如果你沒有采取這個額外的步驟,Visual Basic .NET編譯器將為你做這些工作,添加新的委托類型到類的元數據中去。FileSearch5.Search方法利用了這一機制,在每個文件找到時調用OnFileFound方法:
’’ 來自 FileSearch5.vb
Dim afi() As FileInfo = diLocal.GetFiles(Me.FileSpec)
For Each fi As FileInfo In afi
OnFileFound(fi)
Next |
點擊示例窗體上的Event Design Pattern按鈕創建一個FileSearch5類的實例并調用它的Execute方法(正如所有前面的例子一樣)。在此情況下,FileSearch5.FileFound事件的事件處理程序是有點不同:不是只接受一個FileInfo對象,這個事件處理程序看起來像一個標準的.NET事件處理程序;它接受兩個參數并使用了FileFoundEventArgs參數的 FileFound屬性來顯示找到的文件名稱:
’’ From frmMain.vb
Private Sub fs5_FileFound( _
ByVal sender As Object, ByVal e As FileFoundEventArgs) _
Handles fs5.FileFound
AddText(e.FileFound.FullName)
End Sub |
盡管你不需要使用標準.NET事件處理設計模式,它總是使得你自己的事件與內部.NET對象引發的事件匹配的最好。你獲得在你的事件偵聽器中“通曉”的好處,并且它們看起來像其他事件。創建你自己的事件委托是可選的,但是如果你有多個事件傳遞相同的參數,使用事件委托可以簡化你的代碼。另外,因為事件參數的更改只能在一個地方進行,所以根據你的需要在任何時候修改你的代碼將會更容易。
動態添加和移除處理程序
到目前為止你所看到的每個事件偵聽器都需要你在設計時關聯到事件處理程序。Handles子句是關聯使用WithEvents關鍵字的對象引發事件的方便而簡單的方法,但是它不能在運行時提供任何彈性。另外,當多個過程處理相同的事件,Handles子句不會給你事件處理程序執行順序的控制權。(當然,為了避免這個問題你可以使用你前面看到的技巧,遍歷調用GetInvocationList返回項。這個技術要求引發事件的類的附加代碼,不是在關聯事件偵聽器的代碼中。)
為了在何時及以什么順序調用事件處理程序上獲得完全主動,你可以使用AddHandler(和RemoveHandler)語句而不是Handles子句。AddHandler和RemoveHandler語句允許你提供一個特定的事件和準備響應事件被調用過程的地址。每個對AddHandler的調用使過程和事件相關聯以使得.NET Framework在事件發生時調用過程。另外,AddHandler總是添加事件處理程序到事件調用列表的末尾。這意味著你控制了事件被處理的順序。
當然,如果你再多考慮片刻,你會理解當你有多個過程時,相同的事件都有一個Handles子句,Visual Basic編譯器為事件處理程序創建一個多路廣播委托實例而不允許你控制它們被添加進的順序。引發的事件調用委托實例的Invoke方法,這時就按每個事件偵聽器被添加的順序(并且你無權控制這個順序)調用它們。當你使用AddHandler和RemoveHandler語句而不是Handles子句,你只要簡單控制各項加入多路廣播委托的順序。每次你的應用程序對相同事件調用AddHandler語句,你就為這個事件添加了一個新的偵聽器到列表的最后。當你引發這個事件,.NET運行時按順序調用每個偵聽器。
如果你點擊了示例窗體的Add/RemoveHandler按鈕,一個新的FileSearch5類的實例被創建,同時實例的FileFound事件的多個事件處理程序被整合(hooked up)。這時,當代碼調用實例的Execute方法,示例窗體的listbox控件顯示出結果:
’’ 來自 frmMain.vb
Dim fs5 As New FileSearch5( _
Me.txtSearchPath.Text, Me.txtFileSpec.Text, _
Me.chkSearchSubfolders.Checked)
AddHandler fs5.FileFound, AddressOf EventHandler7
AddHandler fs5.FileFound, AddressOf EventHandler6
AddHandler fs5.FileFound, AddressOf EventHandler5
AddText("Note the order of invocation:")
fs5.Execute() |
然后,代碼將EventHandler7從回調列表中移去并再次調用execute方法:
RemoveHandler fs5.FileFound, AddressOf EventHandler7
AddText(String.Empty)
AddText("And then there were two:")
fs5.Execute() |
最后,代碼移除剩余的事件處理程序:
RemoveHandler fs5.FileFound, AddressOf EventHandler6
RemoveHandler fs5.FileFound, AddressOf EventHandler5 |
記住,在你調用AddHandler和RemoveHandler語句時你提供地址的過程必須有正確的委托類型。因此,除非你提供給AddHandler 和 RemoveHandler地址的過程參數簽名與事件的參數相符(就是說,除非它們有正確的委托的類型),否則你的代碼將不能被編譯。

圖9 控制事件處理程序回調的順序
|
圖 9顯示了點擊Add/RemoveHandler按鈕后的顯示。正如你所能看到的,事件過程按你將它們添加到回調列表的順序被調用。
Visual Basic中的自定義事件
還記得在一個事件有多個偵聽器時發生的問題,還有一個事件偵聽器拋出一個異常嗎?這個問題有一個相當簡單的
解決方案,用多路廣播委托實例的回調列表關聯到這個事件。然而,在Visual Basic .NET 2002 和2003,這里有一些其他的事件挑戰:(如果)沒有復雜或低效率的代碼,簡單(的方法)將無法體現。Visual Basic 2005以前,事件委托類型的實例總是由Visual Basic編譯器為你創建,并且編譯器無法提供給你修改這個委托實例的行為。
Visual Basic 2005為事件聲明添加了新的Custom關鍵字。這個關鍵字允許你為事件的AddHandler, RemoveHandler和RaiseEvent行為提供代碼。 這取決于你創建適當的委托類型及創建擁有關于事件偵聽器信息的類型的實例。然而,除此之外,你有對你如何處理事件有著完全地控制。
為了在Visual Studio 2005中創建一個自定義事件,在一個類里面放入你的游標,并為你的事件錄入一個聲明,如下:
Public Custom Event <YourEventName> As <EventDelegateType> |
當你完成這行代碼時,編輯器將插入關聯的AddHandler, RemoveHandler和RaiseEvent部分。比如,設想一下,你希望通過創建一個自定義事件處理程序來解決異常問題。示例項目的Visual Basic 2005版的FileSearch6類包含像這樣做的一個自定義FileFound事件。代碼包括適當的事件委托的一個聲明,如下所示:
Public Delegate Sub FileFoundEventHandler( _
ByVal sender As Object, ByVal e As FileFoundEventArgs) |
鍵入事件聲明添加一個空的自定義事件,如圖 10 所示的。
Public Custom Event FileFound As FileFoundEventHandler
AddHandler(ByVal value As FileFoundEventHandler)
End AddHandler
RemoveHandler(ByVal value As FileFoundEventHandler)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As FileFoundEventArgs)
End RaiseEvent
End Event |
這要由你提供事件委托實例的存儲(storage),并提供儲存,移出并回調事件偵聽器的代碼。對于本例,因為代碼需要能單獨回調每個偵聽器并捕捉及處理任何異常,FileSearch6類包括了一個儲存了FileFoundEventHandler實例的泛型列表集合(generic List collection)。每次任何類為這個事件調用AddHandler,或用一個Handles子句捕捉這個事件,Visual Basic運行時引擎調用FileFound 自定義事件的AddHandler部分。代碼必須添加FileFoundEventHandler以傳遞到泛型列表。RemoveHandler部分從內部集合里移去指定的委托實例。RaiseEvent部分調用每個委托實例的Invoke方法,捕捉和處理發生的異常。完全的自定義事件看起來如圖 11 所示的代碼。
Private listeners As New List(Of FileFoundEventHandler)
Public Custom Event FileFound As FileFoundEventHandler
AddHandler(ByVal value As FileFoundEventHandler)
listeners.Add(value)
End AddHandler
RemoveHandler(ByVal value As FileFoundEventHandler)
If listeners.Contains(value) Then
listeners.Remove(value)
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As FileFoundEventArgs)
For Each listener As FileFoundEventHandler In listeners
Try
listener.Invoke(sender, e)
Catch ex As Exception
’ Something goes wrong? Just move on to the next handler.
End Try
Next
End RaiseEvent
End Event
|
通過推進代碼到事件聲明本身,引發事件的代碼不再需要擔心處理異常的問題了。這就是說,與使用你前面看到的代碼來引發事件不同,代碼假定在Visual Basic 2005中的FileSearch6類可以簡單地調用OnFileFound方法(它來引發事件)或直接調用RaiseEvent。擔心異常的責任現在適得其所:它就在事件代碼本身之中。這項技術直到Visual Basic 2005才可以使用。注意Visual Studio 2005現仍在beta版(
測試中)。正因為如此,到最終版本發布前其細節會有變化。
還有其它可以用到自定義事件嗎?Rocky Lhotka,一個Visual Basic MVP,在他的blog上包括另一個詳細的例子( .NET 2.0 solution to serialization of objects that raise events )。他論述了 你可能會用到這個技術來解決涉及引發事件類序列化而偵聽器沒有序列化的問題。(令人驚訝的是,這經常發生,因為窗體沒有序列化,但是常常用戶創建事件的偵聽器是序列化的。)Paul Vick,作為Microsoft的Visual Basic
開發團隊的成員之一,他的blog上包括一個例子顯示你如何可以使用一個 自定義事件來減少暴露給大量事件而只有很少的事件可能會用到的類的系統開銷。對于窗體來說就是這個情況,例如—窗體類暴露于大量事件,但是大多數時間里,你只會處理它們中一或兩個。沒有某些技巧,編譯器將為每個事件引發一個委托實例,盡管你不會使用它們。請看Paul的blog在 Custom events。
原文轉自:http://www.anti-gravitydesign.com
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97
|