附加事件概觀 (WPF .NET)

可延伸的應用程式標記語言 (XAML) 定義稱為 附加事件的語言元件和事件種類。 附加事件可用來在非元素類別中定義新的 路由事件 ,並在樹狀結構中的任何專案上引發該事件。 若要這樣做,您必須將附加事件註冊為路由事件,並提供支援附加事件功能的特定 支援程式碼 。 由於附加事件會註冊為路由事件,因此在元素上引發時,它們會透過專案樹狀結構傳播。

重要

.NET 6 和 .NET 5 的桌面指南檔 (包括 .NET Core 3.1) 正在進行建構。

Prerequisites

本文假設已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: ,前置詞是必要的,因為 AquariumFilterAquarium 類別存在於不同的 Common Language Runtime (CLR) 命名空間和元件中。

您也可以在程式碼後置中附加事件的處理常式。 若要這樣做,請在處理常式應該附加的物件上呼叫 AddHandler 方法,並將事件識別碼和處理常式當做參數傳遞至 方法。

WPF 如何實作附加事件

WPF 附加事件會實作為欄位支援的 RoutedEvent 路由事件。 因此,附加事件會在引發專案樹狀結構之後傳播。 一般而言,引發附加事件的物件稱為事件來源,是系統或服務來源。 系統或服務來源不是專案樹狀結構的直接部分。 對於其他附加事件,事件來源可能是樹狀結構中的元素,例如複合控制項內的元件。

附加事件案例

在 WPF 中,附加事件用於有服務層級抽象概念的特定功能區域。 例如,WPF 會使用靜態 MouseValidation 類別所啟用的附加事件。 與或服務互動的類別可以使用附加事件語法與事件互動,或將附加事件呈現為路由事件。 後者選項是類別如何整合服務的功能的一部分。

WPF 輸入系統會大量使用附加事件。 不過,幾乎所有附加事件都會透過基底元素呈現為相等的非附加路由事件。 每個路由輸入事件都是基底專案類別的成員,並且會以 CLR 事件 「wrapper」 支援。 您很少會直接使用或處理附加事件。 例如,透過對等 UIElement.MouseDown 路由事件處理 的基礎 UIElement 附加 Mouse.MouseDown 事件比在 XAML 或程式碼後置中使用附加事件語法更容易。

附加事件可藉由啟用未來輸入裝置的擴充,來提供架構用途。 例如,新的輸入裝置只需要引發 Mouse.MouseDown 來模擬滑鼠輸入,而且不需要衍生自 Mouse 以執行此動作。 該案例牽涉到事件的程式碼處理,因為附加事件的 XAML 處理不會相關。

處理附加事件

撰寫和處理附加事件的程式基本上與非附加路由事件的程式相同。

先前所述,現有的 WPF 附加事件通常不適合直接在 WPF 中處理。 通常,附加事件的用途是讓複合控制項內的元素將其狀態報表給控制項內的父元素。 在該案例中,事件會在程式碼中引發,並依賴相關父類別中的類別處理。 例如,內 Selector 的專案預期會引發 Selected 附加事件,然後由 類別處理的 Selector 類別。 類別 Selector 可能會將 Selected 事件 SelectionChanged 轉換成路由事件。 如需路由事件和類別處理的詳細資訊,請參閱 將路由事件標示為已處理,以及類別處理

定義自訂附加事件

如果您要衍生自常見的 WPF 基類,您可以在 類別中包含兩個存取子方法,以實作自訂附加事件。 這些方法是:

  • Addevent < nameHandler >方法,其中第一個參數是附加事件處理常式的專案,以及要加入之事件處理常式的第二個參數。 方法必須是 publicstatic ,且沒有傳回值。 方法會 AddHandler 呼叫基類方法,以引數的形式傳入路由事件和處理常式。 這個方法支援 XAML 屬性語法,可將事件處理常式附加至 專案。 這個方法也會啟用附加事件的事件處理常式存放區的程式碼存取。

  • Removeevent < nameHandler >方法,其第一個參數是附加事件處理常式的專案,以及要移除之事件處理常式的第二個參數。 方法必須是 publicstatic ,且沒有傳回值。 方法會 RemoveHandler 呼叫基類方法,以引數的形式傳入路由事件和處理常式。 這個方法可讓程式碼存取附加事件的事件處理常式存放區。

WPF 會實作附加事件做為路由事件,因為 的識別碼 RoutedEvent 是由 WPF 事件系統所定義。 此外,路由事件是附加事件的 XAML 語言層級概念的自然延伸。 此實作策略會將附加事件的處理限制為 UIElement 衍生類別或 ContentElement 衍生類別,因為只有那些類別具有 AddHandler 實作。

例如,下列程式碼會在 Clean 擁有者類別上 AquariumFilter 定義附加事件,這不是元素類別。 程式碼會將附加事件定義為路由事件,並實作必要的存取子方法。

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 事件「包裝函式」,附加的事件存取子方法可以在未衍生自 UIElementContentElement 的類別中實作。 這是可行的,因為附加的事件備份程式碼會在傳入 UIElement 的 實例上呼叫 UIElement.AddHandlerUIElement.RemoveHandler 方法。 相反地,非附加路由事件的 CLR 包裝函式會直接在擁有類別上呼叫這些方法,讓類別必須衍生自 UIElement

引發 WPF 附加事件

引發附加事件的程式基本上與非附加路由事件的程式相同。

一般而言,您的程式碼不需要引發任何現有的 WPF 定義附加事件,因為這些事件遵循一般「服務」概念模型。 在該模型中,服務類別,例如 InputManager ,負責引發 WPF 定義的附加事件。

使用以路由事件為基礎的附加事件的 WPF 模型定義自訂附加事件時,請使用 UIElement.RaiseEvent 方法在任何 或 ContentElementUIElement 引發附加事件。 引發路由事件時,不論是否附加路由事件,都必須將元素樹狀結構中的元素指定為事件來源。 然後, RaiseEvent 該來源會回報為呼叫端。 例如,若要在 上 aquarium1 引發 AquariumFilter.Clean 附加路由事件:

aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))

在上述範例中, aquarium1 是事件來源。

另請參閱