方法 : ContextMenuOpening イベントを処理する

イベント データの Handled プロパティを true に設定すると、アプリケーション内で ContextMenuOpening イベントを処理することによって、既存のコンテキスト メニューを表示前に調整したり、表示されないようにしたりすることができます。 一般に、イベント データの Handled を true に設定するのは、メニュー全体を新しい ContextMenu オブジェクトに置き換えるために行い、これには操作の取り消しと新しいオープン操作の開始が必要になることがあります。 ContextMenuOpening イベントのハンドラーを作成する場合、ContextMenu コントロールと、コントロール全般のコンテキスト メニューのオープンと配置に応答するサービスとの間のタイミングの問題について理解している必要があります。 このトピックでは、コンテキスト メニューのさまざまな開き方に対応するコード テクニックを示し、タイミングの問題が関係する状況について説明します。

ContextMenuOpening イベントの処理には、次のようなシナリオがあります。

  • 表示前にメニュー項目を調整する。

  • 表示前にメニュー全体を置き換える。

  • 既存のどのコンテキスト メニューも表示されないようにする。

使用例

表示前にメニュー項目を調整する

既存のメニュー項目の調整は比較的単純で、最も一般的なシナリオです。 アプリケーションの現在の状態の情報や、コンテキスト メニューの対象となるオブジェクトのプロパティとして取得される特定の状態情報に応じて、コンテキスト メニューのオプションを増減する場合に行います。

一般的な方法は、まずイベントのソース (右クリックされた特定のコントロール) を取得し、そのソースの ContextMenu プロパティを取得する方法です。 通常、Items コレクションを調べてコンテキスト メニュー内にどのメニュー項目が既に存在するかを確認し、このコレクションに適切な新しいメニュー項目 MenuItem を追加するか、このコレクションからメニュー項目を削除します。

        Private Sub AddItemToCM(ByVal sender As Object, ByVal e As ContextMenuEventArgs)
            'check if Item4 is already there, this will probably run more than once
            Dim fe As FrameworkElement = TryCast(e.Source, FrameworkElement)
            Dim cm As ContextMenu = fe.ContextMenu
            For Each mi As MenuItem In cm.Items
                If CType(mi.Header, String) = "Item4" Then
                    Return
                End If
            Next mi
            Dim mi4 As New MenuItem()
            mi4.Header = "Item4"
            fe.ContextMenu.Items.Add(mi4)
        End Sub
void AddItemToCM(object sender, ContextMenuEventArgs e)
{
    //check if Item4 is already there, this will probably run more than once
    FrameworkElement fe = e.Source as FrameworkElement;
    ContextMenu cm = fe.ContextMenu;
    foreach (MenuItem mi in cm.Items)
    {
        if ((String)mi.Header == "Item4") return;
    }
    MenuItem mi4 = new MenuItem();
    mi4.Header = "Item4";
    fe.ContextMenu.Items.Add(mi4);
}

表示前にメニュー全体を置き換える

もう 1 つのシナリオは、コンテキスト メニュー全体を置き換える場合です。 もちろん、前の方法を応用して、既存のコンテキスト メニューからすべてのメニュー項目を削除し、新しいメニュー項目を項目 0 から順に追加することもできます。 しかし、コンテキスト メニューのすべての項目を置き換えるよりわかりやすい方法は、新しい ContextMenu を作成してこれに新しい項目を追加してから、コントロールの FrameworkElement.ContextMenu プロパティを、作成した新しい ContextMenu になるように設定することです。

次のコードは、ContextMenu を置き換える単純なハンドラーのコードです。 このコードで参照しているカスタムの BuildMenu メソッドが分離されているのは、複数のサンプル ハンドラーからこれを呼び出すためです。

        Private Sub HandlerForCMO(ByVal sender As Object, ByVal e As ContextMenuEventArgs)
            Dim fe As FrameworkElement = TryCast(e.Source, FrameworkElement)
            fe.ContextMenu = BuildMenu()
        End Sub
void HandlerForCMO(object sender, ContextMenuEventArgs e)
{
    FrameworkElement fe = e.Source as FrameworkElement;
    fe.ContextMenu = BuildMenu();
}
        Private Function BuildMenu() As ContextMenu
            Dim theMenu As New ContextMenu()
            Dim mia As New MenuItem()
            mia.Header = "Item1"
            Dim mib As New MenuItem()
            mib.Header = "Item2"
            Dim mic As New MenuItem()
            mic.Header = "Item3"
            theMenu.Items.Add(mia)
            theMenu.Items.Add(mib)
            theMenu.Items.Add(mic)
            Return theMenu
        End Function
ContextMenu BuildMenu()
{
    ContextMenu theMenu = new ContextMenu();
    MenuItem mia = new MenuItem();
    mia.Header = "Item1";
    MenuItem mib = new MenuItem();
    mib.Header = "Item2";
    MenuItem mic = new MenuItem();
    mic.Header = "Item3";
    theMenu.Items.Add(mia);
    theMenu.Items.Add(mib);
    theMenu.Items.Add(mic);
    return theMenu;
}

ただし、この手法を ContextMenuOpening のハンドラーに適用する場合は、ContextMenu を設定するオブジェクトに既存のコンテキスト メニューが存在しないと、タイミングの問題が発生することがあります。 ユーザーがコントロールを右クリックすると、既存の ContextMenu が空または null の場合も ContextMenuOpening が発生します。 しかし、このような場合は、ソース要素に設定した新しい ContextMenu の取得が遅れるので、表示に間に合いません。 また、ユーザーがここで再度右クリックすると、今度は新しい ContextMenu が表示され、値は null でなくなり、ハンドラーの 2 回目の実行でメニューが正しく置き換えられて表示されます。 これを回避するには、次の 2 つの方法が考えられます。

  1. ContextMenuOpening ハンドラーが実行されるコントロールには常に ContextMenu のプレースホルダーが少なくとも 1 個存在するようにし、このプレースホルダーをハンドラー コードで置き換えます。 この場合、前の例のハンドラーを使用できますが、通常は、最初のマークアップでプレースホルダー ContextMenu を割り当てる必要があります。

    <StackPanel>
      <Rectangle Fill="Yellow" Width="200" Height="100" ContextMenuOpening="HandlerForCMO">
        <Rectangle.ContextMenu>
          <ContextMenu>
            <MenuItem>Initial menu; this will be replaced ...</MenuItem>
          </ContextMenu>
        </Rectangle.ContextMenu>
      </Rectangle>
      <TextBlock>Right-click the rectangle above, context menu gets replaced</TextBlock>
    </StackPanel>
    
  2. 事前のロジックに基づいて、ContextMenu の初期値が null である場合を想定します。 ContextMenu が null であるかを調べることもでき、また、ハンドラーが少なくとも 1 回は実行されているかどうかを示すフラグをコード内で使用するという方法もあります。 ContextMenu が表示されるものと想定した場合、ハンドラーでイベント データの Handled を true に設定します。 コンテキスト メニューの表示に応答する ContextMenuService に対し、イベント データの Handled の値 true は、そのイベントを発生させたコンテキスト メニューおよびコントロールの組み合わせの表示取り消し要求を表します。

これで、問題のありそうなコンテキスト メニューは表示しないようにしたので、次は、新しいコンテキスト メニューを設定してそれを表示します。 新しいコンテキスト メニューの設定は、基本的には前のハンドラーと同じです。新しい ContextMenu を作成し、コントロールのソースの FrameworkElement.ContextMenu プロパティに設定します。 この場合、最初の表示処理を取り消しているので、新しいコンテキスト メニューを強制的に表示する追加手順が必要になります。 強制的に表示するには、ハンドラー内で Popup.IsOpen プロパティを true に設定します。 この場合、ハンドラー内でコンテキスト メニューを開くと ContextMenuOpening イベントが再度発生することになるので、注意が必要です。 ハンドラーに再入すると無限再帰を引き起こします。 ContextMenuOpening イベント ハンドラー内からコンテキスト メニューを開く場合に必ず、null かどうかを調べたり、フラグを使用したりする必要があるのはこのためです。

既存のどのコンテキスト メニューも表示されないようにする

メニューを一切表示しないようにするハンドラーの作成という最後のシナリオは、一般的ではありません。 あるコントロールでコンテキスト メニューを表示しないようにする場合、ユーザーが要求したときにそのメニューが表示されないようにするよりも他に適切な方法があると考えられます。 それでも、ハンドラーを使用してコンテキスト メニューが一切表示されないようにする場合は、ハンドラーでイベント データの Handled を true に設定するだけでできます。 コンテキスト メニューの表示に応答する ContextMenuService は、コントロールに関して発生したイベントのイベント データを調べます。 いずれかの時点でそのイベントの Handled がオンになっていた場合、そのイベントを発生させたコンテキスト メニューのオープン操作は抑制されます。

        Private Sub HandlerForCMO2(ByVal sender As Object, ByVal e As ContextMenuEventArgs)
            If Not FlagForCustomContextMenu Then
                e.Handled = True 'need to suppress empty menu
                Dim fe As FrameworkElement = TryCast(e.Source, FrameworkElement)
                fe.ContextMenu = BuildMenu()
                FlagForCustomContextMenu = True
                fe.ContextMenu.IsOpen = True
            End If
        End Sub
    End Class
        void HandlerForCMO2(object sender, ContextMenuEventArgs e)
        {
            if (!FlagForCustomContextMenu)
            {
                e.Handled = true; //need to suppress empty menu
                FrameworkElement fe = e.Source as FrameworkElement;
                fe.ContextMenu = BuildMenu();
                FlagForCustomContextMenu = true;
                fe.ContextMenu.IsOpen = true;
            }
        }
    }

参照

参照

ContextMenu

FrameworkElement.ContextMenu

概念

基本要素の概要

ContextMenu の概要