关于silverlight的ContextMenu问题

最近做一个项目遇到一个问题,就是在silverlight做的bingmap上有几个maplayer,每个maplayer上都有一个右键菜单contextmenu,然后出现问题,每次只有上层的contextmenu响应,下层的不是不显示就是只显示一次,经过搜索,大体了解是什么原因了

先来了解下什么是WPF里的路由事件

我们创建一个WPF应用程序,代码如下:

<Window x:Class="Wpfceshi.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" MouseDown="Window_MouseDown"  >
    <Grid MouseDown="Grid_MouseDown" x:Name="grid">
        <Button Height="30" Width="100" Content="点击我" MouseDown="Button_MouseDown"/>
    </Grid>
</Window>

 

using System.Windows;
using System.Windows.Input;

namespace Wpfceshi
{
    /// <summary>
    /// Window1.xaml 的交互逻辑
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Window被点击");
        }

        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Grid被点击");
        }

        private void Button_MouseDown(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Button被点击");
        }
    }
}

调试运行,鼠标右键点击按钮,会依次弹出三个对话框。(注意一定是鼠标右键,否则引发不了事件)

这里大家也许就会问了,我点击的是按钮,为什么Grid和Window也会引发事件呢?其实这就是路由事件的机制,引发的事件由源元素逐级传到上层的元素,Button—>Grid—>Window,这样就导致这几个元素都接收到了事件。

那么如何让Grid和Window不处理这个事件呢?

我们只需要在Button_MouseDown这个方法中加上e.Handled = true; 这样就表示事件已经被处理,其他元素不需要再处理这个事件了。

private void Button_MouseDown(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Button被点击");
            e.Handled = true;
        }

这时如果我们需要Grid也参与处理这个事件该怎么做呢?我们只需要给他AddHandler即可。

修改代码如下

public Window1()
        {
            InitializeComponent();
            grid.AddHandler(Grid.MouseDownEvent, new RoutedEventHandler(Grid_MouseDown1), true);
        }

再加上这个方法

private void Grid_MouseDown1(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Grid被点击");
        }

上面这个方法有时候会用到,比如有时候需要使用MouseLeftButtonDown事件响应,默认是不响应的

到此大家应该对路由事件有大概的认识了吧。

什么是路由事件?

可以从功能或实现的角度来考虑路由事件。此处对这两种定义均进行了说明,因为用户当中有的认为前者更有用,而有的则认为后者更有用。

功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。

实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统来处理。

路由策略路由事件使用以下三个路由策略之一:

冒泡:针对事件源调用事件处理程序。 路由事件随后会路由到后续的父元素,直到到达元素树的根。大多数路由事件都使用冒泡路由策略。冒泡路由事件通常用来报告来自不同控件或其他 UI 元素的输入或状态变化。

直接:只有源元素本身才有机会调用处理程序以进行响应。 这与 Windows 窗体用于事件的“路由”相似。However, unlike a standard CLR event, direct routed events support class handling (class handling is explained in an upcoming section) and can be used by EventSetter and EventTrigger.

隧道:最初将在元素树的根处调用事件处理程序。 随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。在合成控件的过程中通常会使用或处理隧道路由事件,这样,就可以有意地禁止显示复合部件中的事件,或者将其替换为特定于整个控件的事件。在 WPF 中提供的输入事件通常是以隧道/冒泡对实现的。隧道事件有时又称作 Preview 事件,这是由隧道/冒泡对所使用的命名约定决定的。

为什么使用路由事件?作为应用程序开发人员,您不需要始终了解或关注正在处理的事件是否作为路由事件实现。路由事件具有特殊的行为,但是,如果您在引发该行为的元素上处理事件,则该行为通常会不可见。

如果您使用以下任一建议方案,路由事件的功能将得到充分发挥:在公用根处定义公用处理程序、合成自己的控件或者定义您自己的自定义控件类。

路由事件侦听器和路由事件源不必在其层次结构中共享公用事件。任何 UIElement 或 ContentElement 可以是任一路由事件的事件侦听器。 因此,您可以使用在整个工作 API 集内可用的全套路由事件作为概念“接口”,应用程序中的不同元素凭借这个接口来交换事件信息。路由事件的这个“接口”概念特别适用于输入事件。

路由事件还可以用来通过元素树进行通信,因为事件的事件数据会永存到路由中的每个元素中。一个元素可以更改事件数据中的某项内容,该更改将对于路由中的下一个元素可用。

之所以将任何给定的 WPF 事件作为路由事件实现(而不是作为标准 CLR 事件实现),除了路由方面的原因,还有两个其他原因。如果您要实现自己的事件,则可能也需要考虑这两个因素:

Certain WPF styling and templating features such as EventSetter and EventTrigger require the referenced event to be a routed event. 前面提到的事件标识符方案就是这样的。

路由事件支持类处理机制,类可以凭借该机制来指定静态方法,这些静态方法能够在任何已注册的实例程序访问路由事件之前,处理这些路由事件。这在控件设计中非常有用,因为您的类可以强制执行事件驱动的类行为,以防它们在处理实例上的事件时被意外禁止。 

RoutedEventHandler 是基本的路由事件处理程序委托。 对于针对某些控件或方案而专门处理的路由事件,要用于路由事件处理程序的委托还可能会变得更加专用化,因此它们可以传输专用的事件数据。例如,在常见的输入方案中,可以处理 DragEnter 路由事件。 您的处理程序应当实现 DragEventHandler 委托。 借助更具体的委托,可以对处理程序中的 DragEventArgs 进行处理,并读取 Data 属性,该属性中包含拖动操作的剪贴板内容。

在用代码创建的应用程序中为路由事件添加处理程序非常简单。路由事件处理程序始终可以通过帮助器方法 AddHandler 来添加,现有支持为 add 调用的也是此方法。但是,现有的 WPF 路由事件通常借助于支持机制来实现 add 和 remove 逻辑,这些逻辑允许使用特定于语言的事件语法来添加路由事件的处理程序,特定于语言的事件语法比帮助器方法更直观。
下面是帮助器方法的示例用法:
void MakeButton()
 {
     Button b2 = new Button();
     b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
 }
 void Onb2Click(object sender, RoutedEventArgs e)
 {
     //logic to handle the Click event    
 }

事件的处理顺序如下所示:

针对根元素处理 PreviewMouseDown(隧道)。

针对中间元素 1 处理 PreviewMouseDown(隧道)。

针对源元素 2 处理 PreviewMouseDown(隧道)。

针对源元素 2 处理 MouseDown(冒泡)。

针对中间元素 1 处理 MouseDown(冒泡)。

针对根元素处理 MouseDown(冒泡)。

路由事件处理程序委托提供对以下两个对象的引用:引发该事件的对象以及在其中调用处理程序的对象。在其中调用处理程序的对象是由 sender 参数报告的对象。 首先在其中引发事件的对象是由事件数据中的 Source 属性报告的。 路由事件仍可以由同一个对象引发和处理,在这种情况下, sender 和 Source 是相同的(事件处理示例列表中的步骤 3 和 4 就是这样的情况)。

由于存在隧道和冒泡,因此父元素接收 Source 作为其子元素之一的输入事件。 当有必要知道源元素是哪个元素时,可以通过访问 Source 属性来标识源元素。

通常,一旦将输入事件标记为 Handled,就将不进一步调用处理程序。 通常,一旦调用了用来对输入事件的含义进行特定于应用程序的逻辑处理的处理程序,就应当将输入事件标记为“已处理”。

 

EventSetter 和 EventTrigger在样式中,可以通过使用 EventSetter 在标记中包括某个预先声明的 XAML 事件处理语法。 在应用样式时,所引用的处理程序会添加到带样式的实例中。
下面是一个示例。请注意,此处引用的 b1SetColor 方法位于代码隐藏文件中。

XAML:
<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.EventOvw2"
  Name="dpanel2"
  Initialized="PrimeHandledToo"
>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <EventSetter Event="Click" Handler="b1SetColor"/>
    </Style>
  </StackPanel.Resources>
  <Button>Click me</Button>
  <Button Name="ThisButton" Click="HandleThis">
    Raise event, handle it, use handled=true handler to get it anyway.
  </Button>
</StackPanel>


这样做的好处在于,样式有可能包含大量可应用于应用程序中任何按钮的其他信息,让 EventSetter 成为该样式的一部分甚至可以提高代码在标记级别的重用率。 而且, EventSetter 还进一步从通用的应用程序和页面标记中提取处理程序方法的名称。

另一个将 WPF 的路由事件和动画功能结合在一起的专用语法是 EventTrigger。 与 EventSetter 一样,只有路由事件可以用于 EventTrigger。 通常将 EventTrigger 声明为样式的一部分,但是还可以在页面级元素上将 EventTrigger 声明为 Triggers 集合的一部分或者在 ControlTemplate 中对其进行声明。 使用 EventTrigger,可以指定当路由事件到达其路由中的某个元素(这个元素针对该事件声明了 EventTrigger)时将运行的 Storyboard。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值