WPF 如何控制右键菜单ContextMenu的弹出
在具体做一些项目的时候,有时候需要需要先左键点击某个节点,然后再右键点击节点的时候才弹出右键菜单,所以直接右键点击时需要禁用掉右键菜单,这里比如我们为Grid添加了ContextMenu,但是我们需要设置一个bool型的变量isSelected,当我们执行到MouseLeftButtonDown事件中的时候,我们就可以将isSelected设置为true,然后在Grid中添加PreviewMouseRightButtonUp="OnMouseRightButtonUp"(隧道事件路由)事件或者是MouseRightButtonUp(冒泡事件路由)事件,在本项目中两者都可以,但是不要添加在<Grid.ContextMenu/>及其子节点下面,这个稍后会介绍,然后在主程序中执行下面的代码:
private void OnMouseRightButtonUp(object sender,MouseButtonEventArgs e) { if(isSelected==true) { //使消息可以传递 e.Handled=false; } else { e.Handled=true; } isSelected=false; }
这样当我们先左键点击某一个节点的时候,然后再点击右键的时候才能弹出ContextMenu,这里我们需要正确理解Handled的含义,Handled将路由事件标记为已处理的值。如果 Handled 的值为 true,则可以防止事件路由路径上的大多数处理程序再次处理同一事件。这在实际的事件处理中是非常有用的,因为WPF的这种层层嵌套的UI设计思想,使得某一个事件,例如UIElement.MouseLeftButtonDown在很多元素上都会触发,根据事件的不同类型,可以有冒泡触发或者是隧道的这种方式来触发,但事实上只有一个元素最终执行该事件,如果某一个元素已经执行过该事件,那么它就会将Handled 的值设为 true,这样事件就不会向下或者向上路由了,如果想让事件继续路由就要将Handled设置为false这个是非常重要的,下面的内容参考一下别人写的一篇博客,很有意义。
在读《WPF高级编程》,看到事件的上传和下传。有个例子:
前台代码:
<Window x:Class="TunnelingBubbling.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" > <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown" PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown" Width="313" Height="271" Name="mygrid"> <Button PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown" MouseLeftButtonDown="Button_MouseLeftButtonDown" Click="Button_Click" Name="btnGo"> <TextBox Name="textBox1" Width="173" Height="27" MouseLeftButtonDown="textBox1_MouseLeftButtonDown" PreviewMouseLeftButtonDown="textBox1_PreviewMouseLeftButtonDown"> </TextBox> </Button> </Grid> </Window>
分别为Grid,Button,TextBox定义了PreviewMouseLeftButtonDown和MouseLeftButtonDown事件,并且为Button定义的Click事件。
后台代码如下:
public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("Grid_PreviewMouseLeftButtonDown"); } private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("Button_PreviewMouseLeftButtonDown"); } private void textBox1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("textBox1_PreviewMouseLeftButtonDown"); } private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("Grid_MouseLeftButtonDown"); } private void Button_Click(object sender, RoutedEventArgs e) { Debug.WriteLine("Button_Click"); } private void textBox1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("textBox1_MouseLeftButtonDown"); } private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("Button_MouseLeftButtonDown"); } }
但是在执行的时候,MouseLeftButtonDown事件总是无法触发。查看了一些资料,才知道这是一个设计原则上的问题。控件在捕获了MouseLeftButtonDown事件后,会将该事件的“Handled”设置为True,这个属性是用在事件路由中的,当某个控件得到一个RoutedEvent,就会检测Handled是否为true,为true则忽略该事件。
并且,控件本身的Click事件,相当于将MouseLeftButtonDown事件抑制(Supress)掉了,转换成了Click事件。所以,如果一定要使用这个事件的话,需要在初始化的函数里利用UIElement的AddHandler方法,显式的增加这个事件。
---------方法说明:---------------------------------------------------------------------------------
UIElement.AddHandler 方法 (RoutedEvent, Delegate, Boolean)
为指定的路由事件添加路由事件处理程序,并将该处理程序添加到当前元素的处理程序集合中。将 handledEventsToo 指定为 true 时,可为已标记为由其他元素在事件路由过程中处理的路由事件调用所提供的处理程序。
参数说明:
handledEventsToo类型:System..::.Boolean
如果为 true,则将按以下方式注册处理程序:即使路由事件在其事件数据中标记为已处理,也会调用该处理程序;如果为 false,则使用默认条件注册处理程序,即当路由事件被标记为已处理时,将不调用处理程序。
默认值为 false。
修改后的程序代码如下:
public Window1() { InitializeComponent(); mygrid.AddHandler(Grid.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.Grid_MouseLeftButtonDown), true); btnGo.AddHandler(Button.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.Button_MouseLeftButtonDown), true); textBox1.AddHandler(TextBox.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.textBox1_MouseLeftButtonDown), true); }
再次运行,调用成功,事件的执行顺序是:
次序 |
控件 |
事件 |
事件类型 |
1 |
Grid | PreviewMouseLeftButtonDown |
下传 |
2 |
Button | PreviewMouseLeftButtonDown |
下传 |
3 |
TextBox | PreviewMouseLeftButtonDown |
下传 |
4 |
TextBox | MouseLeftButtonDown |
上传 |
5 |
Button | MouseLeftButtonDown |
上传 |
6 |
Grid | MouseLeftButtonDown |
上传 |
如果点击Button,则事件的执行顺序是:
次序 |
控件 |
事件 |
事件类型 |
1 |
Grid | PreviewMouseLeftButtonDown |
下传 |
2 |
Button | PreviewMouseLeftButtonDown |
下传 |
3 |
Button | MouseLeftButtonDown |
上传 |
4 |
Grid | MouseLeftButtonDown |
上传 |
5 |
Button | Click |
|
最后补充一点:
在事件处理程序中,可以取消事件处理,查找事件的源元素
例如在下面的代码中:
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine("Button_PreviewMouseLeftButtonDown"); // e.Handled = true; // Button myButton = e.Source as Button; }
1.取消事件处理: 设置 e.Handled = true;
2.在事件处理程序中查找源元素: e.Source 传递一个实际对象实例,可以将其强制转换为System.Windows.Controls.Button类型,通过这个变量,可以访问这个控件提供的所有本地属性,方法和事件。