附加事件概觀 (WPF .NET)

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

重要

.NET 7 和 .NET 6 的桌面指南檔正在建置中。

必要條件

本文假設您已瞭解 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 基類,您可以在 類別中包含兩個存取子方法,以實作自訂附加事件。 這些方法是:

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

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

WPF 實作附加事件做為路由事件,因為 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 是事件來源。

另請參閱