方法 : ContextMenuOpening イベントを処理する
更新 : 2007 年 11 月
イベント データの Handled プロパティを true に設定すると、アプリケーション内で ContextMenuOpening イベントを処理することによって、既存のコンテキスト メニューを表示前に調整したり、表示されないようにしたりすることができます。一般に、イベント データの Handled を true に設定するのは、メニュー全体を新しい 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);
}
表示前にメニュー全体を置き換える
もう 1 つのシナリオは、コンテキスト メニュー全体を置き換える場合です。もちろん、前の方法を応用して、既存のコンテキスト メニューからすべてのメニュー項目を削除し、新しいメニュー項目を項目 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 の取得が遅れるので、表示に間に合いません。また、ユーザーがここで再度右クリックすると、今度は新しい ContextMenu が表示され、値は null でなくなり、ハンドラの 2 回目の実行でメニューが正しく置き換えられて表示されます。これを回避するには、次の 2 つの方法が考えられます。
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>
事前のロジックに基づいて、ContextMenu の初期値が null である場合を想定します。ContextMenu が null であるかを調べることもでき、また、ハンドラが少なくとも 1 回は実行されているかどうかを示すフラグをコード内で使用するという方法もあります。ContextMenu が表示されるものと想定した場合、ハンドラでイベント データの Handled を true に設定します。コンテキスト メニューの表示に応答する ContextMenuService に対し、イベント データの Handled の値 true は、そのイベントを発生させたコンテキスト メニューおよびコントロールの組み合わせの表示取り消し要求を表します。
これで、問題のありそうなコンテキスト メニューは表示しないようにしたので、次は、新しいコンテキスト メニューを設定してそれを表示します。新しいコンテキスト メニューの設定は、基本的には前のハンドラと同じです。新しい ContextMenu を作成し、コントロールのソースの FrameworkElement.ContextMenu プロパティに設定します。この場合、最初の表示処理を取り消しているので、新しいコンテキスト メニューを強制的に表示する追加手順が必要になります。強制的に表示するには、ハンドラ内で Popup.IsOpen プロパティを true に設定します。この場合、ハンドラ内でコンテキスト メニューを開くと ContextMenuOpening イベントが再度発生することになるので、注意が必要です。ハンドラに再入すると無限再帰を引き起こします。ContextMenuOpening イベント ハンドラ内からコンテキスト メニューを開く場合に必ず、null かどうかを調べたり、フラグを使用したりする必要があるのはこのためです。
既存のどのコンテキスト メニューも表示されないようにする
メニューを一切表示しないようにするハンドラの作成という最後のシナリオは、一般的ではありません。あるコントロールでコンテキスト メニューを表示しないようにする場合、ユーザーが要求したときにそのメニューが表示されないようにするよりも他に適切な方法があると考えられます。それでも、ハンドラを使用してコンテキスト メニューが一切表示されないようにする場合は、ハンドラでイベント データの Handled を true に設定するだけでできます。コンテキスト メニューの表示に応答する 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;
}
}
}