次の方法で共有


ルーティング イベントを処理済みとしてマークし、クラスレベルで処理を行う

ルーティング イベントをいつ処理済みとしてマークするかに関する絶対的な規則はありませんが、コードがイベントに重要な方法で応答する場合は、イベントを処理済みとしてマークすることを検討してください。 処理済みとしてマークされたルーティング イベントは、そのルートに沿って続行されますが、処理されたイベントに応答するように構成されたハンドラーのみが呼び出されます。 基本的に、ルーティング イベントを処理済みとしてマークすると、イベント ルートに沿ったリスナーへの可視性が制限されます。

ルーティング イベント ハンドラーには、インスタンス ハンドラーまたはクラス ハンドラーのいずれかを指定できます。 インスタンス ハンドラーは、オブジェクトまたは XAML 要素のルーティング イベントを処理します。 クラス ハンドラーは、クラス レベルでルーティング イベントを処理し、クラスの任意のインスタンスで同じイベントに応答するインスタンス ハンドラーの前に呼び出されます。 ルーティング イベントが処理済みとしてマークされると、多くの場合、クラス ハンドラー内でそのようにマークされます。 この記事では、ルーティング イベントを処理済みとしてマークする利点と潜在的な落とし穴、ルーティング イベントとルーティング イベント ハンドラーの種類、複合コントロールでのイベント抑制について説明します。

[前提条件]

この記事では、ルーティング イベントに関する基本的な知識と、 ルーティング イベントの概要を読んだことを前提としています。 この記事の例に従うと、拡張アプリケーション マークアップ言語 (XAML) に慣れている場合や、Windows Presentation Foundation (WPF) アプリケーションを記述する方法を理解している場合に役立ちます。

ルーティングイベントを「処理済み」としてマークする際の判断基準

通常、ルーティングされたイベントごとに 1 つのハンドラーのみが重要な応答を提供する必要があります。 ルーティング イベント システムを使用して、複数のハンドラー間で重要な応答を提供しないようにします。 重要な応答を構成するものの定義は主観的であり、アプリケーションによって異なります。 一般的なガイダンス:

  • 重要な応答には、フォーカスの設定、パブリック状態の変更、ビジュアル表現に影響するプロパティの設定、新しいイベントの発生、イベントの完全な処理が含まれます。
  • 重要でない応答には、ビジュアルやプログラムに影響を与えずにプライベート状態を変更する、イベント ログを記録する、イベントに応答せずにイベント データを調べるなどがあります。

一部の WPF コントロールでは、それ以上処理する必要のないコンポーネント レベルのイベントを、処理されたイベントとしてマークすることで抑制されます。 コントロールによって処理済みとしてマークされたイベントを処理する場合は、「コントロール によるイベント抑制の回避」を参照してください。

イベントを 処理済みとしてマークするには、イベント データの Handled プロパティ値を true に設定します。 その値を falseに戻ることは可能ですが、その必要性はまれです。

プレビューおよびバブリングのルーティングイベントのペア

プレビュー イベントとバブル ルーティング イベントのペアは、 入力イベントに固有です。 など、いくつかの入力イベントによってPreviewKeyDownKeyDown ルーティング イベントのペアが実装されます。 Preview プレフィックスは、プレビュー イベントが完了するとバブル イベントが開始されることを示します。 プレビュー イベントとバブル イベントの各ペアは、イベント データの同じインスタンスを共有します。

ルーティング イベント ハンドラーは、イベントのルーティング戦略に対応する順序で呼び出されます。

  1. プレビュー イベントは、アプリケーションのルート要素から、ルーティング イベントを発生させた要素まで移動します。 アプリケーション ルート要素にアタッチされたプレビュー イベント ハンドラーが最初に呼び出され、続いて連続する入れ子になった要素にアタッチされたハンドラーが呼び出されます。
  2. プレビュー イベントが完了すると、ペアのバブル イベントは、ルーティング イベントを発生させた要素からアプリケーションルート要素に移動します。 ルーティング イベントを発生させたのと同じ要素にアタッチされたバブル イベント ハンドラーが最初に呼び出され、続いて連続する親要素にアタッチされたハンドラーが呼び出されます。

ペアのプレビュー イベントとバブル イベントは、独自のルーティング イベントを宣言して発生させるいくつかの WPF クラスの内部実装の一部です。 クラス レベルの内部実装がないと、プレビュー イベントとバブル ルーティング イベントは完全に分離され、イベントの名前付けに関係なく、イベント データは共有されません。 カスタム クラスでバブルまたはトンネリング入力ルーティング イベントを実装する方法については、「 カスタム ルーティング イベントを作成する」を参照してください。

各プレビュー イベントとバブル イベント ペアはイベント データの同じインスタンスを共有するため、プレビュー ルーティング イベントが処理済みとしてマークされている場合は、ペアのバブル イベントも処理されます。 バブル ルーティング イベントが処理済みとしてマークされている場合、プレビュー イベントが完了したため、ペアのプレビュー イベントには影響しません。 プレビューおよびバブル入力イベントのペアを処理済みとしてマークする際には注意してください。 処理されたプレビュー入力イベントは、トンネリング ルートの残りの部分に対して通常登録されているイベント ハンドラーを呼び出しません。ペアのバブル イベントは発生しません。 処理されたバブル入力イベントは、バブル ルートの残りの部分に対して通常登録されているイベント ハンドラーを呼び出しません。

インスタンスとクラス経路指定イベントハンドラー

ルーティング イベント ハンドラーには、 インスタンス ハンドラーまたは クラス ハンドラーのいずれかを指定できます。 特定のクラスのクラス ハンドラーは、そのクラスの任意のインスタンスで同じイベントに応答するインスタンス ハンドラーの前に呼び出されます。 この動作により、ルーティング イベントが処理済みとしてマークされると、多くの場合、クラス ハンドラー内でそのようにマークされます。 クラス ハンドラーには、次の 2 種類があります。

インスタンス イベント ハンドラー

AddHandler メソッドを直接呼び出すことで、インスタンス ハンドラーをオブジェクトまたは XAML 要素にアタッチできます。 WPF ルーティング イベントは、 AddHandler メソッドを使用してイベント ハンドラーをアタッチする共通言語ランタイム (CLR) イベント ラッパーを実装します。 イベント ハンドラーをアタッチするための XAML 属性構文は CLR イベント ラッパーの呼び出しになるため、XAML でハンドラーをアタッチしても AddHandler 呼び出しに解決されます。 処理されたイベントの場合:

  • XAML 属性構文または AddHandler の共通シグネチャを使用してアタッチされたハンドラーは呼び出されません。
  • AddHandler(RoutedEvent, Delegate, Boolean) パラメーターが handledEventsToo に設定されているtrue オーバーロードを使用してアタッチされたハンドラーが呼び出されます。 このオーバーロードは、処理されたイベントに応答する必要があるまれなケースで使用できます。 たとえば、要素ツリー内の一部の要素はイベントを処理済みとしてマークしていますが、イベント ルートに沿った他の要素は、処理されたイベントに応答する必要があります。

次の XAML サンプルでは、componentWrapperという名前のカスタム コントロールを追加し、TextBoxという名前のcomponentTextBoxStackPanel という名前のouterStackPanelにラップします。 PreviewKeyDown イベントのインスタンス イベント ハンドラーは、XAML 属性構文を使用してcomponentWrapperにアタッチされます。 その結果、インスタンス ハンドラーは、PreviewKeyDownによって発生した未処理のcomponentTextBoxトンネリング イベントにのみ応答します。

<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
    <custom:ComponentWrapper
        x:Name="componentWrapper"
        TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
        HorizontalAlignment="Center">
        <TextBox Name="componentTextBox" Width="200" />
    </custom:ComponentWrapper>
</StackPanel>

MainWindow コンストラクターは、KeyDown パラメーターを componentWrapper に設定して、UIElement.AddHandler(RoutedEvent, Delegate, Boolean) オーバーロードを使用して、handledEventsToo バブル イベントのインスタンス ハンドラーをtrueにアタッチします。 その結果、インスタンス イベント ハンドラーは、未処理のイベントと処理されたイベントの両方に応答します。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),
            handledEventsToo: true);
    }

    // The handler attached to componentWrapper in XAML.
    public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) => 
        Handler.InstanceEventInfo(sender, e);
}
Partial Public Class MainWindow
    Inherits Window

    Public Sub New()
        InitializeComponent()

        ' Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf InstanceEventInfo),
                                      handledEventsToo:=True)
    End Sub

    ' The handler attached to componentWrapper in XAML.
    Public Sub HandlerInstanceEventInfo(sender As Object, e As KeyEventArgs)
        InstanceEventInfo(sender, e)
    End Sub

End Class

次のセクションでは、ComponentWrapper のコードビハインドの実装を示します。

静的クラス イベント ハンドラー

クラスの静的コンストラクターで RegisterClassHandler メソッドを呼び出すことで、静的クラス イベント ハンドラーをアタッチできます。 クラス階層内の各クラスは、ルーティング イベントごとに独自の静的クラス ハンドラーを登録できます。 その結果、イベント ルート内の任意のノードで、同じイベントに対して複数の静的クラス ハンドラーが呼び出される可能性があります。 イベントのイベント ルートが構築されると、各ノードのすべての静的クラス ハンドラーがイベント ルートに追加されます。 ノードでの静的クラス ハンドラーの呼び出しの順序は、最も派生した静的クラス ハンドラーから始まり、その後に連続する各基底クラスの静的クラス ハンドラーが続きます。

RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) パラメーターが handledEventsToo に設定されたtrue オーバーロードを使用して登録された静的クラス イベント ハンドラーは、未処理のルーティング イベントと処理されたルーティング イベントの両方に応答します。

静的クラス ハンドラーは通常、未処理のイベントにのみ応答するように登録されます。 その場合、ノード上の派生クラス ハンドラーがイベントを処理済みとしてマークした場合、そのイベントの基底クラス ハンドラーは呼び出されません。 このシナリオでは、基底クラス ハンドラーは実質的に派生クラス ハンドラーに置き換えられます。 基底クラス ハンドラーは、多くの場合、外観、状態ロジック、入力処理、コマンド処理などの領域でのデザインの制御に役立ちます。そのため、置き換えることに注意してください。 イベントを処理済みとしてマークしない派生クラス ハンドラーは、基底クラス ハンドラーを置き換える代わりに補足します。

次のコード サンプルは、前の XAML で参照された ComponentWrapper カスタム コントロールのクラス階層を示しています。 ComponentWrapper クラスは、ComponentWrapperBase クラスから派生し、StackPanel クラスから派生します。 RegisterClassHandlerクラスとComponentWrapper クラスの静的コンストラクターで使用される ComponentWrapperBase メソッドは、これらの各クラスの静的クラス イベント ハンドラーを登録します。 WPF イベント システムは、ComponentWrapper静的クラス ハンドラーの前にComponentWrapperBase静的クラス ハンドラーを呼び出します。

public class ComponentWrapper : ComponentWrapperBase
{
    static ComponentWrapper()
    {
        // Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfo_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfo_Override(this, e);

        // Call the base OnKeyDown implementation on ComponentWrapperBase.
        base.OnKeyDown(e);
    }
}

public class ComponentWrapperBase : StackPanel
{
    // Class event handler implemented in the static constructor.
    static ComponentWrapperBase()
    {
        EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfoBase_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfoBase_Override(this, e);

        e.Handled = true;
        Debug.WriteLine("The KeyDown routed event is marked as handled.");

        // Call the base OnKeyDown implementation on StackPanel.
        base.OnKeyDown(e);
    }
}
Public Class ComponentWrapper
    Inherits ComponentWrapperBase

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapper), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfo_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfo_Override(Me, e)

        ' Call the base OnKeyDown implementation on ComponentWrapperBase.
        MyBase.OnKeyDown(e)
    End Sub

End Class

Public Class ComponentWrapperBase
    Inherits StackPanel

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapperBase), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfoBase_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfoBase_Override(Me, e)

        e.Handled = True
        Debug.WriteLine("The KeyDown event is marked as handled.")

        ' Call the base OnKeyDown implementation on StackPanel.
        MyBase.OnKeyDown(e)
    End Sub

End Class

このコード サンプルのオーバーライド クラス イベント ハンドラーの分離コード実装については、次のセクションで説明します。

クラス イベント ハンドラーをオーバーライドする

一部のビジュアル要素の基底クラスでは、パブリック ルーティング入力イベントごとに 空の On<event name> および OnPreview<event name> 仮想メソッドが公開されます。 たとえば、 UIElement は、 OnKeyDownOnPreviewKeyDown の仮想イベント ハンドラーなどを実装します。 基底クラスの仮想イベント ハンドラーをオーバーライドして、派生クラスのオーバーライド クラス イベント ハンドラーを実装できます。 たとえば、DragEnter仮想メソッドをオーバーライドすることで、任意のUIElement派生クラスにOnDragEnter イベントのオーバーライド クラス ハンドラーを追加できます。 基底クラスの仮想メソッドのオーバーライドは、クラス ハンドラーを静的コンストラクターに登録するよりも簡単にクラス ハンドラーを実装する方法です。 オーバーライド内では、イベントを発生させたり、クラス固有のロジックを開始してインスタンスの要素プロパティを変更したり、イベントを処理済みとしてマークしたり、その他のイベント処理ロジックを実行したりできます。

静的クラス イベント ハンドラーとは異なり、WPF イベント システムは、クラス階層内の最も派生クラスのオーバーライド クラス イベント ハンドラーのみを呼び出します。 クラス階層内で最も派生したクラスは、 基本 キーワードを使用して仮想メソッドの基本実装を呼び出すことができます。 ほとんどの場合、イベントを処理済みとしてマークするかどうかに関係なく、基本実装を呼び出す必要があります。 基底実装ロジックを置き換える必要があるクラスがある場合にのみ、基本実装の呼び出しを省略する必要があります。 オーバーライドするコードの前または後に基本実装を呼び出すかどうかは、実装の性質によって異なります。

前のコード サンプルでは、基底クラス OnKeyDown 仮想メソッドは、 ComponentWrapper クラスと ComponentWrapperBase クラスの両方でオーバーライドされます。 WPF イベント システムは ComponentWrapper.OnKeyDown オーバーライド クラス イベント ハンドラーのみを呼び出すので、そのハンドラーは base.OnKeyDown(e) を使用してクラス イベント ハンドラーを ComponentWrapperBase.OnKeyDown オーバーライドし、 base.OnKeyDown(e) を使用して StackPanel.OnKeyDown 仮想メソッドを呼び出します。 前のコード サンプルのイベントの順序は次のとおりです。

  1. componentWrapperにアタッチされたインスタンス ハンドラーは、PreviewKeyDownルーティング イベントによってトリガーされます。
  2. componentWrapperにアタッチされた静的クラス ハンドラーは、KeyDownルーティング イベントによってトリガーされます。
  3. componentWrapperBaseにアタッチされた静的クラス ハンドラーは、KeyDownルーティング イベントによってトリガーされます。
  4. componentWrapperにアタッチされたオーバーライド クラス ハンドラーは、KeyDownルーティング イベントによってトリガーされます。
  5. componentWrapperBaseにアタッチされたオーバーライド クラス ハンドラーは、KeyDownルーティング イベントによってトリガーされます。
  6. KeyDownルーティング イベントは、処理済みとしてマークされます。
  7. componentWrapperにアタッチされたインスタンス ハンドラーは、KeyDownルーティング イベントによってトリガーされます。 ハンドラーは、handledEventsToo パラメーターが true に設定されて登録されました。

複合コントロールでの入力イベント抑制

一部の複合コントロールは、コンポーネント レベルで 入力イベント を抑制し、より多くの情報を運ぶか、より具体的な動作を意味するカスタマイズされた高レベルのイベントに置き換えます。 複合コントロールは、定義上、複数の実用的なコントロールまたはコントロールの基底クラスで構成されます。 従来の例として、さまざまなマウス イベントをButtonルーティング イベントに変換するClick コントロールがあります。 Button基底クラスはButtonBaseであり、UIElementから間接的に派生します。 制御入力処理に必要なイベント インフラストラクチャの多くは、 UIElement レベルで利用できます。 UIElementは、MouseMouseLeftButtonDownなど、いくつかのMouseRightButtonDownイベントを公開します。 UIElement は、空の仮想メソッドである OnMouseLeftButtonDown を実装し、このメソッドを事前登録されたクラスハンドラーとして OnMouseRightButtonDown も実装します。 ButtonBase は、これらのクラス ハンドラーをオーバーライドし、オーバーライド ハンドラー内で Handled プロパティを true に設定し、 Click イベントを発生させます。 ほとんどのリスナーの最終的な結果は、 MouseLeftButtonDown イベントと MouseRightButtonDown イベントが非表示になり、高レベルの Click イベントが表示されます。

入力イベント抑制を回避する方法

個々のコントロール内のイベント抑制が、アプリケーションのイベント処理ロジックに干渉する場合があります。 たとえば、アプリケーションで XAML 属性構文を使用して XAML ルート要素の MouseLeftButtonDown イベントのハンドラーをアタッチした場合、 Button コントロールによって MouseLeftButtonDown イベントが処理済みとしてマークされるため、そのハンドラーは呼び出されません。 処理されたルーティング イベントに対してアプリケーションのルートに向かって要素を呼び出す場合は、次のいずれかを実行できます。

  • UIElement.AddHandler(RoutedEvent, Delegate, Boolean) パラメーターを handledEventsToo に設定して、true メソッドを呼び出してハンドラーをアタッチします。 この方法では、アタッチ先の要素のオブジェクト参照を取得した後、コードビハインド内でイベントハンドラーをアタッチする必要があります。

  • 処理済みとしてマークされたイベントがバブル入力イベントである場合は、ペアのプレビュー イベントのハンドラーをアタッチします (使用可能な場合)。 たとえば、コントロールが MouseLeftButtonDown イベントを抑制する場合は、代わりに PreviewMouseLeftButtonDown イベントのハンドラーをアタッチできます。 この方法は、イベント データを共有するプレビューとバブル入力イベントのペアでのみ機能します。 PreviewMouseLeftButtonDown イベントが完全に抑制されるため、Clickを処理済みとしてマークしないように注意してください。

入力イベントの抑制を回避する方法の例については、「 コントロールによるイベント抑制の回避」を参照してください。

こちらも参照ください