附加事件概觀 (WPF .NET)
Extensible Application Markup Language (XAML) 會定義語言元件和稱為附加事件的事件類型。 附加事件可用來在非元素類別中定義新的路由事件,並在樹狀結構中的任何元素上引發該事件。 若要這樣做,您必須將附加事件註冊為路由事件,並提供支援附加事件功能的特定支援程式碼。 由於附加事件註冊為路由事件,因此在元素上引發時,會透過元素樹狀結構散佈。
必要條件
本文假設您已基本了解 Windows Presentation Foundation (WPF) 路由事件,而且您已閱讀路由事件概觀和 WPF 中的 XAML。 若要遵循本文中的範例,建議您先熟悉 XAML,並了解如何撰寫 WPF 應用程式。
附加事件語法
在 XAML 語法中,附加事件是由其事件名稱和其擁有者類型所指定,格式為 <owner type>.<event name>
。 因為事件名稱限定為其擁有者類型的名稱,因此語法可讓事件附加至可實例化的任何元素。 對於附加至事件路由中任意元素的一般路由事件,此語法也適用於其處理常式。
下列 XAML 屬性語法會將 AquariumFilter.Clean
附加事件的 AquariumFilter_Clean
處理常式附加至 aquarium1
元素:
<aqua:Aquarium x:Name="aquarium1" Height="300" Width="400" aqua:AquariumFilter.Clean="AquariumFilter_Clean"/>
在此範例中,aqua:
字首是必要的,因為 AquariumFilter
和 Aquarium
類別存在於不同的通用語言執行平台 (CLR) 命名空間和組件中。
您也可以在程式碼後置中為附加事件附加處理常式。 若要這樣做,請在應該附加處理常式的物件上呼叫 AddHandler 方法,並將事件識別碼和處理常式當做參數傳遞至該方法。
WPF 如何實作附加事件
WPF 附加事件會實作為 RoutedEvent 欄位所支援的路由事件。 因此,附加事件會在引發之後,透過元素樹狀結構散佈。 一般而言,引發附加事件的物件,又稱為事件來源,是系統或服務來源。 系統或服務來源不是元素樹狀結構的直接部分。 對於其他附加事件,事件來源可能是樹狀結構中的元素,例如複合控制項內的元件。
附加事件案例
在 WPF 中,附加事件用於具有服務層級抽象概念的特定功能區域。 例如,WPF 會使用靜態 Mouse 或 Validation 類別所啟用的附加事件。 與服務互動或使用服務的類別可以使用附加事件語法與事件互動,或將附加事件呈現為路由事件。 後者是類別如何整合服務功能的一部分。
WPF 輸入系統會廣泛使用附加事件。 不過,幾乎所有附加事件都會透過基礎元素呈現為相等的非附加路由事件。 每個路由輸入事件都是基底元素類別的成員,並由 CLR 事件「包裝函式」支援。 您很少會直接使用或處理附加事件。 例如,透過相等的 UIElement.MouseDown 路由事件在 UIElement 上處理基礎附加 Mouse.MouseDown 事件,比在 XAML 或程式碼後置中使用附加事件語法更為容易。
附加事件可藉由啟用輸入裝置的未來擴充,來提供架構用途。 例如,新的輸入裝置只需要引發 Mouse.MouseDown
即可模擬滑鼠輸入,而無需從 Mouse
衍生即可執行此動作。 該案例與事件的程式碼處理有關,因為與附加事件的 XAML 處理無關。
處理附加事件
編碼和處理附加事件的流程基本上與非附加路由事件的流程相同。
如先前所述,現有的 WPF 附加事件通常不打算在 WPF 中直接處理。 更常見的是,附加事件的用途是讓複合控制項內的元素能夠向控制項內的父元素報告其狀態。 在該案例中,事件會在程式碼中引發,並依賴相關父類別中的類別處理。 例如,Selector 內的項目預期會引發 Selected 附加事件,然後由 Selector
類別進行類別處理。 Selector
類別可能會將 Selected
事件轉換成 SelectionChanged 路由事件。 如需路由事件與類別處理的詳細資訊,請參閱將路由事件標記為已處理以及類別處理。
定義自訂附加事件
如果您要衍生自一般 WPF 基底類別,您可以在您的類別中包含兩個存取子方法,以實作自訂附加事件。 這些方法是:
Add<event name>Handler 方法,第一個參數是附加事件處理常式的元素,第二個參數是要新增的事件處理常式。 方法必須是
public
和static
,且沒有傳回值。 方法會呼叫 AddHandler 基底類別方法,以引數的形式傳入路由事件和處理常式。 此方法支援 XAML 屬性語法,以將事件處理常式附加至元素。 此方法也可讓程式碼存取附加事件的事件處理常式存放區。Remove<event name>Handler 方法,第一個參數是附加事件處理常式的元素,第二個參數是要刪除的事件處理常式。 方法必須是
public
和static
,且沒有傳回值。 方法會呼叫 AddHandler 基底類別方法,以引數的形式傳入路由事件和處理常式。 此方法可讓程式碼存取附加事件的事件處理常式存放區。
WPF 會將附加事件實作為路由事件,因為 RoutedEvent 的識別碼是由 WPF 事件系統所定義。 此外,路由事件是附加事件的 XAML 語言層級概念的自然擴充。 此實作策略會將附加事件的處理限制為 UIElement 衍生類別或 ContentElement 衍生類別,因為只有這些類別具有 AddHandler 實作。
例如,下列程式碼會在 AquariumFilter
擁有者類別上定義 Clean
附加事件,這不是元素類別。 程式碼會將附加事件定義為路由事件,並實作必要的存取子方法。
public class AquariumFilter
{
// Register a custom routed event using the bubble routing strategy.
public static readonly RoutedEvent CleanEvent = EventManager.RegisterRoutedEvent(
"Clean", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));
// Provide an add handler accessor method for the Clean event.
public static void AddCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
{
if (dependencyObject is not UIElement uiElement)
return;
uiElement.AddHandler(CleanEvent, handler);
}
// Provide a remove handler accessor method for the Clean event.
public static void RemoveCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
{
if (dependencyObject is not UIElement uiElement)
return;
uiElement.RemoveHandler(CleanEvent, handler);
}
}
Public Class AquariumFilter
' Register a custom routed event using the bubble routing strategy.
Public Shared ReadOnly CleanEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
"Clean", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))
' Provide an add handler accessor method for the Clean event.
Public Shared Sub AddCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)
If uiElement IsNot Nothing Then
uiElement.[AddHandler](CleanEvent, handler)
End If
End Sub
' Provide a remove handler accessor method for the Clean event.
Public Shared Sub RemoveCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)
If uiElement IsNot Nothing Then
uiElement.[RemoveHandler](CleanEvent, handler)
End If
End Sub
End Class
傳回附加事件識別碼的 RegisterRoutedEvent 方法同於用來註冊非附加路由事件的方法。 附加和非附加路由事件都會註冊到集中式內部存放區。 此事件存放區實作啟用了路由事件概觀中所討論的「事件即介面」概念。
不同於用來支援非附加路由事件的 CLR 事件「包裝函式」,附加事件存取子方法可以在不是從 UIElement 或 ContentElement 衍生的類別中實作。 這是可能的,因為附加事件支援程式碼會在傳入的 UIElement
實例上呼叫 UIElement.AddHandler 和 UIElement.RemoveHandler 方法。 相反地,非附加路由事件的 CLR 包裝函式會直接在擁有類別上呼叫這些方法,因此類別必須衍生自 UIElement
。
觸發 WPF 附加事件
引發附加事件的流程基本上與非附加路由事件的流程相同。
一般而言,您的程式碼不需要引發 WPF 已定義的任何現有附加事件,因為這些事件遵循一般「服務」概念模型。 在該模型中,InputManager 等服務類別負責引發 WPF 已定義的附加事件。
使用以路由事件為基礎之附加事件的 WPF 模型來定義自訂附加事件時,請使用 UIElement.RaiseEvent 方法在任何 UIElement 或 ContentElement 上引發附加事件。 引發路由事件時,不論是否為附加路由事件,您都必須將元素樹狀結構中的元素指定為事件來源。 然後,該來源會回報為 RaiseEvent
呼叫者。 例如,若要在 aquarium1
上引發 AquariumFilter.Clean
附加路由事件:
aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))
在上述範例中,aquarium1
是事件來源。