如何:处理 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 的对象没有预先具备上下文菜单时,可能会出现计时问题。 用户右键单击控件时,即使现有的 ContextMenu 为空或 NULL,也会引发 ContextMenuOpening。 但在这种情况下,在源元素上设置的任何新的 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 值可能为空,这取决于一些初步逻辑。 可以检查 ContextMenu 是否为 null,或使用代码中的标志检查处理程序是否已至少运行一次。 由于假设 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;
}
}
}