カスタム ルーティング イベントを作成する方法 (WPF .NET)

Windows Presentation Foundation (WPF) のアプリケーション開発者とコンポーネント作成者は、カスタム ルーティング イベントを作成して、共通言語ランタイム (CLR) イベントの機能を拡張することができます。 ルーティング イベント機能の詳細については、「ルーティング イベントを使用する理由」を参照してください。 この記事では、カスタム ルーティング イベント作成の基本について説明します。

重要

.NET 7 と .NET 6 用のデスクトップ ガイド ドキュメントは作成中です。

前提条件

この記事では、ルーティング イベントの基本的な知識があり、「ルーティング イベントの概要」をお読みになったことを前提としています。 この記事の例について理解するには、Extensible Application Markup Language (XAML) を使い慣れていて、Windows Presentation Foundation (WPF) アプリケーションの記述方法を理解していると役に立ちます。

ルーティング イベントの手順

ルーティング イベントを作成する基本的な手順は次のとおりです。

  1. RegisterRoutedEvent メソッドを使用して RoutedEvent を登録します。

  2. 登録呼び出しは、登録されたイベント名、ルーティング戦略、およびその他のイベントの詳細を保持し、ルーティング イベント識別子と呼ばれる RoutedEvent インスタンスを返します。 識別子を静的な読み取り専用フィールドに割り当てます。 次のように慣例に従います。

    • バブル戦略を持つルーティング イベントの識別子には、<event name>Event という名前が付けられます。 たとえば、イベント名が Tap の場合は、識別子に TapEvent という名前を付ける必要があります。
    • トンネリング戦略を持つルーティング イベントの識別子には、Preview<event name>Event という名前が付けられます。 たとえば、イベント名が Tap の場合は、識別子に PreviewTapEvent という名前を付ける必要があります。
  3. CLR の addremove のイベント アクセサーを定義します。 CLR イベント アクセサーを使用しない場合、イベント ハンドラーを追加または削除するには、UIElement.AddHandler メソッドと UIElement.RemoveHandler メソッドへの直接呼び出しを使用する必要があります。 CLR イベント アクセサーを使用すると、次のイベント ハンドラーの割り当てメカニズムを取得できます。

    • Extensible Application Markup Language (XAML) の場合、属性構文を使用してイベント ハンドラーを追加できます。
    • C# の場合、+= 演算子と -= 演算子を使用してイベント ハンドラーを追加または削除できます。
    • VB の場合、AddHandler ステートメントと RemoveHandler ステートメントを使用してイベント ハンドラーを追加または削除できます。
  4. ルーティング イベントをトリガーするためのカスタム ロジックを追加します。 たとえば、ロジックによって、ユーザー入力とアプリケーションの状態に基づいてイベントをトリガーできます。

次の例では、カスタム コントロール ライブラリに CustomButton クラスを実装します。 Button から派生した CustomButton クラスで、次の処理を実行します。

  1. RegisterRoutedEvent メソッドを使用して ConditionalClick という名前の RoutedEvent を登録し、登録時にバブル戦略を指定します。
  2. 登録呼び出しから返された RoutedEvent インスタンスを、ConditionalClickEvent という名前の静的な読み取り専用フィールドに割り当てます。
  3. CLR の addremove のイベント アクセサーを定義します。
  4. CustomButton がクリックされ、外部条件が適用されたときにカスタム ルーティング イベントを発生させるカスタム ロジックを追加します。 このコード例では、オーバーライドされた OnClick 仮想メソッド内から ConditionalClick ルーティング イベントを発生させていますが、任意の方法でイベントを発生させることができます。
public class CustomButton : Button
{
    // Register a custom routed event using the Bubble routing strategy.
    public static readonly RoutedEvent ConditionalClickEvent = EventManager.RegisterRoutedEvent(
        name: "ConditionalClick",
        routingStrategy: RoutingStrategy.Bubble,
        handlerType: typeof(RoutedEventHandler),
        ownerType: typeof(CustomButton));

    // Provide CLR accessors for assigning an event handler.
    public event RoutedEventHandler ConditionalClick
    {
        add { AddHandler(ConditionalClickEvent, value); }
        remove { RemoveHandler(ConditionalClickEvent, value); }
    }

    void RaiseCustomRoutedEvent()
    {
        // Create a RoutedEventArgs instance.
        RoutedEventArgs routedEventArgs = new(routedEvent: ConditionalClickEvent);

        // Raise the event, which will bubble up through the element tree.
        RaiseEvent(routedEventArgs);
    }

    // For demo purposes, we use the Click event as a trigger.
    protected override void OnClick()
    {
        // Some condition combined with the Click event will trigger the ConditionalClick event.
        if (DateTime.Now > new DateTime())
            RaiseCustomRoutedEvent();

        // Call the base class OnClick() method so Click event subscribers are notified.
        base.OnClick();
    }
}
Public Class CustomButton
    Inherits Button

    ' Register a custom routed event with the Bubble routing strategy.
    Public Shared ReadOnly ConditionalClickEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        name:="ConditionalClick",
        routingStrategy:=RoutingStrategy.Bubble,
        handlerType:=GetType(RoutedEventHandler),
        ownerType:=GetType(CustomButton))

    ' Provide CLR accessors to support event handler assignment.
    Public Custom Event ConditionalClick As RoutedEventHandler

        AddHandler(value As RoutedEventHandler)
            [AddHandler](ConditionalClickEvent, value)
        End AddHandler

        RemoveHandler(value As RoutedEventHandler)
            [RemoveHandler](ConditionalClickEvent, value)
        End RemoveHandler

        RaiseEvent(sender As Object, e As RoutedEventArgs)
            [RaiseEvent](e)
        End RaiseEvent

    End Event

    Private Sub RaiseCustomRoutedEvent()

        ' Create a RoutedEventArgs instance.
        Dim routedEventArgs As New RoutedEventArgs(routedEvent:=ConditionalClickEvent)

        ' Raise the event, which will bubble up through the element tree.
        [RaiseEvent](routedEventArgs)

    End Sub

    ' For demo purposes, we use the Click event as a trigger.
    Protected Overrides Sub OnClick()

        ' Some condition combined with the Click event will trigger the ConditionalClick event.
        If Date.Now > New DateTime() Then RaiseCustomRoutedEvent()

        ' Call the base class OnClick() method so Click event subscribers are notified.
        MyBase.OnClick()

    End Sub
End Class

この例には、XAML マークアップを使用して CustomButton のインスタンスを StackPanel に追加し、CustomButton 要素と StackPanel1 要素の ConditionalClick イベント ハンドラーとして Handler_ConditionalClick メソッドを割り当てる別個の WPF アプリケーションが含まれています。

<Window x:Class="CodeSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:WpfControl;assembly=WpfControlLibrary"
        Title="How to create a custom routed event" Height="100" Width="300">

    <StackPanel Name="StackPanel1" custom:CustomButton.ConditionalClick="Handler_ConditionalClick">
        <custom:CustomButton
            Name="customButton"
            ConditionalClick="Handler_ConditionalClick"
            Content="Click to trigger a custom routed event"
            Background="LightGray">
        </custom:CustomButton>
    </StackPanel>
</Window>

分離コードでは、WPF アプリケーションによって Handler_ConditionalClick イベント ハンドラー メソッドが定義されます。 イベント ハンドラー メソッドは、分離コードでのみ実装できます。

// The ConditionalClick event handler.
private void Handler_ConditionalClick(object sender, RoutedEventArgs e)
{
    string senderName = ((FrameworkElement)sender).Name;
    string sourceName = ((FrameworkElement)e.Source).Name;

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.");
}

// Debug output when CustomButton is clicked:
// Routed event handler attached to CustomButton,
//     triggered by the ConditionalClick routed event raised on CustomButton.
// Routed event handler attached to StackPanel1,
//     triggered by the ConditionalClick routed event raised on CustomButton.
' The ConditionalClick event handler.
Private Sub Handler_ConditionalClick(sender As Object, e As RoutedEventArgs)

    Dim sourceName As String = CType(e.Source, FrameworkElement).Name
    Dim senderName As String = CType(sender, FrameworkElement).Name

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.")

End Sub

' Debug output when CustomButton is clicked:
' Routed event handler attached to CustomButton,
'     triggered by the ConditionalClick routed event raised on CustomButton.
' Routed event handler attached to StackPanel1,
'     triggered by the ConditionalClick routed event raised on CustomButton.

CustomButton がクリックされた場合、次の処理が実行されます。

  1. ConditionalClick ルーティング イベントが CustomButton で発生します。
  2. CustomButton にアタッチされた Handler_ConditionalClick イベント ハンドラーがトリガーされます。
  3. ConditionalClick ルーティング イベントでは、要素ツリーが StackPanel1 まで検査されます。
  4. StackPanel1 にアタッチされた Handler_ConditionalClick イベント ハンドラーがトリガーされます。
  5. ConditionalClick ルーティング イベントは要素ツリー上で継続され、他の検査対象要素にアタッチされた他の ConditionalClick イベント ハンドラーがトリガーされる可能性があります。

Handler_ConditionalClick イベント ハンドラーは、トリガーしたイベントに関する次の情報を取得します。

  • イベント ハンドラーがアタッチされた要素である sender オブジェクト。 sender は、ハンドラーが初めて実行されたときは CustomButton、2 回目は StackPanel1 になります。
  • 最初にイベントを発生させた要素である RoutedEventArgs.Source オブジェクト。 この例では、Source は常に CustomButton です。

注意

ルーティング イベントと CLR イベントの主な違いは、ルーティング イベントでは要素ツリーを検査してハンドラーを探すのに対し、CLR イベントでは要素ツリーを検査せず、ハンドラーはイベントを発生させたソース オブジェクトにのみアタッチできることです。 その結果、sender ルーティング イベントには、要素ツリー内の任意の検査対象要素を指定できます。

トンネリング イベントは、バブル イベントと同じ方法で作成できます。ただし、Tunnel へのイベント登録呼び出しでルーティング戦略を設定する点が異なります。 トンネリング イベントの詳細については、「WPF の入力イベント」を参照してください。

参照