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

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

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

一、定义、注册和封装路由事件

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

  例如,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()方法注册的。当注册事件时,需要制定事件的名称、路由类型、定义事件处理程序语法的委托以及拥有事件的类。

  通常,路由事件通过普遍的.NET事件进行封装,从而使用所有.NET语言都能访问他们。事件封装器可使用AddHandler()和RemoveHandler()方法添加和删除已注册的调用程序,这两个方法都在FrameworkElement基类中定义,并被每个WPF元素继承。

二、共享路由事件

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

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

三、引发路由事件

  当然,与所有事件类似,定义类需要在一些情况下引发事件。到底在哪里发生是实现细节。然而,重要的细节是事件不是通过传统的.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事件处理程序都可访问与事件路由相关的信息。

四、处理路由事件

  正如前几章介绍,可以使用多种方法关联事件处理程序。最常用的方法是为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));

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

原文地址:https://www.cnblogs.com/Peter-Luo/p/12235588.html

时间: 2024-09-30 19:05:21

【WPF学习】第十三章 理解路由事件的相关文章

【WPF学习】第一章 XAML介绍

XAML(Extensible Application Markup Language的简写,发音为“zammel”)是用于实例化.NET对象的标记语言.尽管XAML是一种应用于诸多不同问题领域的技术,但其主要作用是构造WPF用户界面.换言之,XAML文档定义了在WPF应用程序中组成窗口的面板.按钮以及各种空间的布局. 一.XAML变体 实际上术语“XAML”有多种含义.到目前为止,我们使用XAML标识整个XAML语言,它是一种基于通用XML语法.专门用于表示一颗.NET对象树的语言(这些对象可

Android学习笔记十三.深入理解fragment(一)

深入理解fragment(一) Fragment是Android3.0引入的新API,可以把Fragment想成Activity中的模块,这个模块有自己的布局,有自己的生命周期,单独处理自己的输入,在Activity运行的时候可以加载或者移除Fragment模块. 可以把Fragment设计成可以在多个Activity中复用的模块,当开发的应用程序同时适用于平板电脑和手机时,可以利用Fragment实现灵活的布局,改善用户体验. 一.Fragment的特征 1.Fragment总是作为Activ

【WPF学习】第九章 使用Canvas面板进行基于坐标的布局

Canvas面板允许使用精确的坐标放置元素,如果设置数据驱动的富窗体和标准对话框,这并非好的选择:但如果需要构建其他一些不同的内容(例如,为图形工具创建创建绘图表面),Canvas面板可能是个有用的工具.Canvas面板还是最轻量级的布局容器.这是因为Canvas面板没有包含任何复杂的布局逻辑,用以改变其子元素的首选尺寸.Canvas面板只是在指定的位置放置其子元素,并且子元素具有所希望的精确尺寸. 为在Canvas面板中定位元素,需要设置Canvas.Left和Canvas.Top附加属性.C

野兽的Angular Api 学习、翻译及理解 - - 键盘事件和鼠标事件

野兽的 ng api 学习 - - ngKeydown/ngKeypress/ngKeyup 和 ngMousedown/ngMouseenter/ngMouseleave/ngMousemove/ngMouseover/ngMouseup ngKeydown/ngKeypress/ngKeyup 该指令在按键按下/按键按下/按键松开时执行指定表达式. ngKeydown和ngKeypress略有不同,目前的测试是ngKeypress针对系统按键是无效的,而ngKeydown可以. ngKeyu

迟到的 WPF 学习 —— 路由事件

1. 理解路由事件:WPF 通过事件路由(event routing)概念增强了传统的事件执行的能力和范围,允许源自某个元素的事件由另一个元素引发,例如,事件路由允许工具栏上的一个按钮点击的事件在被代码处理之前上传到工具栏,再由工具栏上传到所属窗体 2. 定义.注册和包装路由事件:和依赖性属性类似,它由只读的静态字段表示,在一个静态构造函数中注册,并通过一个标准的 .Net 事件定义进行包装.如 Button 的 Click 事件,该事件继承自抽象的 ButtonBase 基类 public a

理解WPF路由事件

(一)什么时路由事件功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件.实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统来处理(二)路由事件的划分(1)冒泡:针对事件源调用事件处理程序.路由事件随后会路由到后续的父元素,直到到达元素树的根.(2)隧道:最初将在元素树的根处调用事件处理程序.随后,路由事件将朝着路由事件

WPF路由事件学习转(二)

在传统的.net中已经有了事件机制了,为什么在WPF中要加入路由事件来取代事件呢,最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,从而有了两个概念,LogicalTree 和 VisualTree,那么它们分别是什么呢,举个例子: 这就是LogicalTree,一个Grid里面镶嵌了其他控件或布局组件,这相当于一棵树中的叶子.而VisualTree呢?它就是一个树中的叶子里面的结构,用放大镜看一下,其实叶子里面的结构也是一颗树结构,这就是VisualTree了,例如 好了,既然W

WPF编程宝典之路由事件(一)

路由事件是更具有传播能力的事件--它们可在元素树中向上冒泡和向下隧道传播,并且沿着传播路径被事件处理程序处理. 可通过传统的方式使用路由事件--通过关联具有正确签名的事件处理程序. 1.定义.注册和封装路由事件 与依赖项属性一样,路由事件由只读的静态字段表示,在静态构造函数中注册,并通过标准的.net事件定义进行封装. 1 public abstract class ButtonBase : ContentControl 2 { 3 //定义事件 4 public static readonly

隧道路由事件

学习WPF以来,一直以为隧道路由事件是由触发控件向其子控件传递的,见天才知道,原来隧道路由事件是从窗口想当前元素传递的! 汗颜啊! 贴上一段示例代码来说明一下: <Grid> <Border Height="50" Width="250" BorderBrush="Gray" BorderThickness="1" > <StackPanel Background="LightGray&