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

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

重要

.NET 6 と .NET 5 (.NET Core 3.1 を含む) 用のデスクトップ ガイド ドキュメントは作成中です。

前提条件

この記事では、ルーティング イベントの基本的な知識があり、「ルーティング イベントの概要」をお読みになったことを前提としています。 この記事の例について理解するには、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 の入力イベント」を参照してください。

参照