【WPF学习】第十三章 理解路由事件

  每一个.NET开发人员都熟悉“事件”的思想——当有意义的事情发生时,由对象(如WPF元素)发送的用于通知代码的消息。WPF经过事件路由(event routing)的概念加强了.NET事件模型。事件路由容许源自某个元素的事件由另外一个元素引起。例如,使用事件路由,来自工具栏按钮的单击事件可在被代码处理以前上传到工具栏,而后上传到包含工具栏的窗口。编程

  事件路由为在最合适的位置编写紧凑的、组织良好的用于处理事件的代码提供了灵活性。要使用WPF内容模型,事件路由也是必需的,内容模型容许使用许多不一样的元素构建简单元素(如按钮),而且这些元素都拥有本身独立的事件集合。函数

1、定义、注册和封装路由事件工具

  WPF事件模型和WPF属性模型很是相似。与依赖项属性同样,路由事件由只读的静态字段表示,在静态构造函数中注册,并经过标准的.NET事件定义进行封装。this

  例如,WPF的Button类提供了你们熟悉的Click事件,该事件继承自抽象的ButtonBase基类。下面的代码说明了该事件是如何被定义和注册的:编码

public abstract class ButtonBase:ContentControl,...
{
    public static readonly RoutedEvent ClickEvent;
    static ButtonBase()
    {
        ButtonBase.ClickEvent=EventManager.RegisterRoutedEvent("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
        ...
    }
    
    public event RoutedEventHandler Click
    {
        add
        {
            base.AddHandler(ButtonBase.ClickEvent,value);
        }
        remove
        {
            base.RemoveHandler(ButtonBase.ClickEvent,value);
        }
    }
    ...
}

  依赖项属性是使用DependencyProperty.Register()方法注册的,而路由事件是使用EvenetManager.RegisterRoutedEvent()方法注册的。当注册事件时,须要制定事件的名称、路由类型、定义事件处理程序语法的委托以及拥有事件的类。spa

  一般,路由事件经过广泛的.NET事件进行封装,从而使用全部.NET语言都能访问他们。事件封装器可以使用AddHandler()和RemoveHandler()方法添加和删除已注册的调用程序,这两个方法都在FrameworkElement基类中定义,并被每一个WPF元素继承。设计

2、共享路由事件code

  与依赖项属性同样,可在类之间共享路由事件的定义。例如,UIElement(该类是全部普通WPF元素的起点)和ContentElement(该类是全部内容元素的起点,内容元素是能够被放入流文档中的单独内容片段)这两个基类都使用了MouseUp事件。MouseUp事件是由System.Windows.Input.Mouse类定义的。UIElement类和ContentElement类只经过Routed-Event.AddOwner()方法重用MouseUp事件:对象

UIElement.MouseUpEvent=Mouse.MouseUpEvent.AddOwner(typeof(UIElement));

3、引起路由事件blog

  固然,与全部事件相似,定义类须要在一些状况下引起事件。到底在哪里发生是实现细节。然而,重要的细节是事件不是经过传统的.NET事件封装器引起的,而是使用RaiseEvent()方法引起事件,全部元素都从UIElement类基础了该方法。下面是来自ButtonBase类深层的代码:

RoutedEventArgs e=new RoutedEventArgs(ButtonBase.ClickEvent,this);
base.RaiseEvent(e);

  RaiseEvent()方法负责为每一个已经经过AddHandler()方法注册的调用程序引起事件。由于AddHandler()方法是公有的,全部调用程序可访问该方法——他们可以经过直接调用AddHandler()方法注册他们本身,也可使用事件封装器。不管使用哪一种方法,当调用RaiseEvent()方法时都会通知他们。

  全部WPF事件都为事件签名使用熟悉的.NET约定。每一个事件处理程序的第一个参数(sender参数)都提供引起该事件的对象的引用。第二个参数是EventArgs对象,该对象与其余全部可能很重要的附加细节绑定在一块儿。例如,MouseUp事件提供了一个MouseEventArgs对象,用于指示当事件发生时按下了哪些鼠标键:

private void img_MouseUp(object sender,MouseButtonEventArgs e)
{
}

  在WPF中,若是事件不须要传递任何额外细节,可以使用RoutedEventArgs类,该类包含了有关如何传递事件的一些细节。若是事件确实须要传递额外的信息,那么须要使用更特殊的继承自RoutedEventArgs的对象(如上面示例中的MouseButtonEventArgs)。由于每一个WPF事件参数都继承自RoutedEventArgs类,因此每一个WPF事件处理程序均可访问与事件路由相关的信息。

4、处理路由事件

  正如前几章介绍,可使用多种方法关联事件处理程序。最经常使用的方法是为XAML标记添加事件特性。事件特性按照想要处理的事件命名,它的值就是事件处理程序方法的名称。下面的示例使用这一语法将Image对象的MouseUp事件链接到名为img_MouseUp的事件处理程序:

<Image Source="a.jpg" Stretch="None" Name="img" MouseUp="img_MouseUp" />

  一般约定以"元素名_事件名"的形式命名事件处理程序方法,但这不是必需的。若是没有为元素定义名称(多是由于不须要在代码的任何地方与元素进行交互),可考虑使用如下形式的事件名称:

<Button Click="cmdOK_Click">OK</Button>

  也可使用代码链接事件。下面的代码和上面给出的XAML标记具备相同的效果:

img.MouseUp+=new MouseButtonEventHandler(img_MouseUp);

  上面的代码建立了一个针对该事件具备正确的签名的委托对象(在该例中,是MouseButtonEventHandler委托的实例),并将该委托指向img_MouseUp()方法。而后将该委托添加到img.MouseUp事件的已注册的事件处理程序列表中。

  C#还容许使用更精简的语法,隐式地建立合适的委托对象:

img.MouseUp+=img_MouseUp;

  若是须要动态建立控件,并在窗口生命周期的某一时刻关联事件处理程序,代码方法是很是有用的。相比而言,在XAML中关联的事件总在窗口对象第一次实例化时就被关联到相应的事件处理程序。代码方法是XAML更简单,更精练,若是计划于非编程人员(如艺术设计人员)合做,这是很是好的。缺点是大量的样板代码会使代码文件变得杂乱五章。

  上面的代码方法依赖与事件封装器,时间封装器调用UIElement.AddHandler()方法。也能够自行经过调用UIElement.AddHandler()方法直接链接事件。下面是一个示例:

img.AddHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

  当使用这种方法时,始终须要建立合适的委托类型(如MouseButtonEventHandler),而不能隐式地建立委托对象(这与经过属性封装器关联事件时不一样)。这是由于UIElement.AddHandler()方法支持全部WPF事件,而且它不知道你想使用的委托类型。

  有些开发人员更喜欢使用定义事件的类的名称,而不是引起事件的类的名称。例以下面的等效语法使得MouseUp事件在UIElement中定义的这一事实更加清晰:

img.AddHandler(UIElment.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

  若是想断开事件处理程序,那么只能使用代码。可以使用-=运算符,以下所示:

img.MouseUp -=img_MouseUp;

  或者使用UIElement.RemoveHandler()方法:

img.RemoveHandler(UIElment.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

  为同一事件屡次链接相同的事件处理程序,在技术角度上可行的,这一般是编码错误的结果(这种状况下,事件处理程序会触发屡次)。若是试图删除已经链接了两次的事件处理程序,事件仍会触发事件处理程序,但只触发一次。

相关文章
相关标签/搜索