次の方法で共有


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

ContextMenuOpening イベントは、アプリケーションで処理して、表示前に既存のコンテキスト メニューを調整するか、Handled プロパティをイベント データに true に設定して表示されるメニューを非表示にすることができます。 通常、イベントデータにおいて Handledtrue に設定する理由は、メニューを新しい ContextMenu オブジェクトに完全に置き換えるためです。この操作には、キャンセルして新たに開くことが必要になる場合があります。 ContextMenuOpening イベントのハンドラーを記述する場合は、ContextMenu コントロールと、一般的にコントロールのコンテキスト メニューを開いて配置するサービスとの間のタイミングの問題に注意する必要があります。 このトピックでは、さまざまなコンテキスト メニューを開くシナリオのコード手法の一部を示し、タイミングの問題が発生するケースを示します。

ContextMenuOpening イベントを処理するには、いくつかのシナリオがあります。

  • 表示前のメニュー項目の調整。

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

  • 既存のコンテキスト メニューを完全に抑制し、コンテキスト メニューを表示しない。

表示前のメニュー項目の調整

既存のメニュー項目の調整は非常に簡単で、おそらく最も一般的なシナリオです。 これは、アプリケーションの現在の状態情報や、コンテキスト メニューが要求されるオブジェクトのプロパティとして使用できる特定の状態情報に応答して、コンテキスト メニュー オプションを追加または減算するために行う場合があります。

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

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);
}

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

別のシナリオとして、コンテキスト メニュー全体を置き換える場合があります。 もちろん、上記のコードのバリエーションを使用して、既存のコンテキスト メニューのすべての項目を削除し、項目 0 から始まる新しい項目を追加することもできます。 しかし、コンテキスト メニューのすべての項目を置き換えるより直感的なアプローチは、新しい ContextMenuを作成し、項目を設定し、コントロールの FrameworkElement.ContextMenu プロパティを新しい ContextMenuに設定することです。

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

void HandlerForCMO(object sender, ContextMenuEventArgs e)
{
    FrameworkElement fe = e.Source as FrameworkElement;
    fe.ContextMenu = BuildMenu();
}
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 は、表示に遅すぎます。 また、ユーザーが 2 回目に右クリックした場合、今度は新しい 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 が表示されようとしていることを前提としているため、ハンドラーはイベント データの trueHandled を設定します。 コンテキスト メニューの表示を担当する ContextMenuService に対して、イベント データ内の Handledtrue 値は、イベントを発生させたコンテキスト メニュー/コントロールの組み合わせの表示を取り消す要求を表します。

疑わしい可能性のあるコンテキスト メニューを非表示にしたら、次の手順では新しいコンテキスト メニューを指定してから表示します。 新しいハンドラーの設定は、基本的に前のハンドラーと同じです。新しい ContextMenu を作成し、それを使用してコントロール ソースの FrameworkElement.ContextMenu プロパティを設定します。 追加の手順では、最初の試行を抑制したため、コンテキスト メニューを強制的に表示する必要があります。 強制的に表示するには、Popup.IsOpen プロパティをハンドラー内で true するように設定します。 ハンドラーでコンテキスト メニューを開くと、ContextMenuOpening イベントが再び発生するため、この操作を行うときは注意が必要です。 ハンドラーを再入力すると、無限に再帰的になります。 このため、ContextMenuOpening イベント ハンドラー内からコンテキスト メニューを開く場合は、常に null を確認するか、フラグを使用する必要があります。

既存のコンテキスト メニューを非表示にし、コンテキスト メニューを表示しない

最後のシナリオでは、メニューを完全に抑制するハンドラーを記述することは一般的ではありません。 特定のコントロールがコンテキスト メニューを表示しないことになっている場合は、ユーザーが要求したときだけメニューを抑制するよりも、これを保証するより適切な方法が考えられます。 ただし、ハンドラーを使用してコンテキストメニューを抑制し何も表示しないようにする場合は、イベントデータで Handledtrue に設定する必要があります。 コンテキスト メニューの表示を担当する ContextMenuService は、コントロールで発生したイベントのイベント データを確認します。 イベントがルート上の任意の場所 Handled マークされている場合、イベントを開始したコンテキスト メニューの開くアクションは抑制されます。

    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;
        }
    }
}

関連項目