如何:處理 ContextMenuOpening 事件
可以在應用程式中處理 ContextMenuOpening 事件,在顯示之前調整現有的 [捷徑功能表],或藉由將事件資料中的 Handled 屬性設定為 true
來隱藏原本會顯示的功能表。 在事件數資料中將 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);
}
在顯示之前取代整個功能表
替代案例是如果您想要取代整個操作功能表。 您當然也可以使用上述程式碼的變化,移除現有操作功能表的每個項目,並從零開始新增項目。 但是,取代操作功能表中所有項目的更直覺方式是建立新的 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 的物件沒有預先存在的操作功能表,可能會暴露計時問題。 當使用者在控制項按右鍵時,即使現有的 ContextMenuOpening 是空的或 Null,也會引發 ContextMenu。 但在此情況下,您在來源元素上設定的任何新 ContextMenu 都會遲到而無法顯示。 此外,如果使用者第二次按右鍵,這次您的新 ContextMenu 出現,此值為非 Null,而您的處理常式會在處理常式第二次執行時正確取代並顯示功能表。 這提示了兩種可能的因應措施:
確保 ContextMenuOpening 處理常式一律針對至少有一個可用的預留位置 ContextMenu 的控制項執行,而您想要由處理常式程式碼取代此預留位置。 在此情況下,您仍然可以使用上一個範例所示的處理常式,但通常您會想要在初始標記中指派預留位置 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,或使用程式碼中的旗標來檢查處理常式是否已至少執行一次。 因為您假設 ContextMenu 即將顯示,因此您的處理常式接著會將 Handled 設定為事件資料中的
true
。 對於負責操作功能表顯示的 ContextMenuService,事件資料中true
的 Handled 值代表取消引發事件之操作功能表/控制項組合的顯示要求。
既然您已隱藏潛在的可疑操作功能表,下一個步驟是提供新的功能表,然後顯示它。 設定新功能表基本上與上一個處理常式相同:您建置新的 ContextMenu,並使用它設定控制項來源的 FrameworkElement.ContextMenu 屬性。 另一個步驟是,您現在必須強制顯示操作功能表,因為您隱藏了第一次嘗試。 若要強制顯示,您可以將 Popup.IsOpen 屬性設定為處理常式內的 true
。 當您這樣做時請小心,因為開啟處理常式中的操作功能表會再次引發 ContextMenuOpening 事件。 如果您重新輸入處理常式,它會變成無限遞歸。 這就是為什麼當您從 null
事件處理常式內開啟操作功能表時,一律需要檢查 ContextMenuOpening 或使用旗標。
隱藏任何現有的操作功能表,而且不顯示任何操作功能表
最後一個案例,撰寫完全隱藏功能表的處理常式並不常見。 如果指定的控制項不應該顯示操作功能表,則可能有比當使用者要求功能表時隱藏它更適當的方法來確保其不顯示。 但是,如果您想要使用處理常式來隱藏操作功能表並且不顯示任何內容,則處理常式應該只將 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;
}
}
}