WPF中的传递事件——出自《IT168》

传递事件

    WPF
.NET 简单事件通知之上添加了很多基础结构。传递事件的设计使得事件可以与元素树一起很好的工作。事件发生后,可以在视觉树和逻辑树自动地进行上下传递,我们不需要添加任何额外的代码。

   
传递事件使得我们不需要过多关注于视觉树,这样封装对于我们理解 WPF 的元素合成非常重要。比如,我们点击一个按钮的事件,在点击的时候我们实际上点击的 是一个 ButtonChrome 或者 TextBlock ,也就是说我们点击的是 Button 的内容元素。正是因为事件可以沿视觉树传递, Button 才发 现这个事件,并且可以处理。因此,我们可以给 Button Content 当中添加任意的元素,而不会对事件有任何的影响。如果没有这样的事件传递,我们 点击 Button 内的元素时,必须手动编写代码触发 Button 点击事件。

   
传递事件的的实现和行为与 Dependency 属性类似。同样,我们看看如何实现简单的传递事件。多数时候,传递事件并不比普通的 .NET 事件难。与 Dependency 属性一样, .NET 语言(除了 XAML )本身并不明白传递目标。这些支持都是基于 WPF API

public class Button { // 传递的事件 public static readonly RoutedEvent ClickEvent; static Button() { // 注册事件 Button.DoubleClickEvent = EventManager.RegisterRoutedEvent(“Click”, RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button)); … } // .NET事件保证 (可选的) public event RoutedEventHandler Click { add { AddHandler(Button.ClickEvent, value); } remove { RemoveHandler(Button.ClickEvent, value); } } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { … // 激发事件 RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this)); … } … }

从上面的实现可以看出,事件与 Dependency 属性有很多相似之处。也是定义一个静态的 RoutedEvent 成员,同样在静态构造函数里注册事件。 为了方便,也包装了一个普通的 .NET 事件。这里的 AddHandler/RemoveHandler 不是从 DependencyObject 派生,而是更高一级的基类 System.Windows.UIElement 。这两个方法为相应的事件添加 / 删除一个委派。在 OnMouseLeftButtonDown 中,我们构造一个事件参数,传入事件源对象 this ,然后调用 RaiseEvent 函数。

   
事件策略和处理函数

   
注册 WPF 事件时,我们需要为传递事件选择一种策略,这个策略指定了事件在元素树中传递的方式。 WPF 支持这样三种策略:

    Tunneling
:事件首先在根元素激发,然后到达树下的每个元素直到源元素(或者有处理函数处理这个事件终止了传递)。
    Bubbling
:事件首先在源元素激发,然后向上直到根元素(或者有处理函数处理这个事件终止了传递)。
    Direct
:事件只在源元素激发。这与普通的 .NET 事件一样,除了参与事件触发器。

   
在上面的例子中,我们注册的事件策略就是 Bubbling
   
传递事件的处理函数的参数与普通 .NET 事件一样。第一个参数 System.Object 表示处理函数依附的元素。第二个的 System.EventArgs 派生类,提供了如下四个有用的属性:

    Source
:逻辑树中激发事件的原始元素。
    OriginalSource
:视觉树中激发事件的原始元素。
    Handled
:布尔值,表示事件是否被处理。
    RoutedEvent
:实际的传递事件对象(比如 Button.ClickEvent )。这个对于相同的处理函数处理多个传递事件时非常有用,可以用来区别传递事件。
    Source
OriginalSource 代表了逻辑树和视觉树对象。这有利于我们进行一些低级控制,但是对于有的事件,不需要区别它们,这两个的值是相同的。

现在,我们看看 WPF 到底是如何处理 Bubbling Tunneling 事件的。最后介绍了 Attached 事件。

   
UIElement 类,预定义了很多的传递事件,比如键盘、鼠标等等。其中大多数是 Bubbling 事件,其中很多的事件都还有一个对应的 Tunneling 事件。所有的 Tunneling 事件都是 Preview 前缀命名,它们都在对应的 Bubbling 事件之前激发。比如 PreviewMouseMove 这个 Tunneling 事件是在 MouseMove 这个 Bubbling 事件之前激发的。

    Tunneling
事件的好处就是可以有机会改变或者取消后面的 Bubbling 事件。 WPF 内建的响应事件只会对 Bubbling 事件进行响应,当然, 前提了 Bubbling Tunneling 同时定义。 这种行为有什么好处呢?看下面的一个例子:比如,我们想实现一种特殊的编辑框,只允许输入一些特定 的字符。以前的实现方法在处理编辑框的 KeyDown 或者编辑框的 WM_CHAR 事件,然后判断新输入的字符是否满足条件,如果不满足,我们再把编辑框的 值设置为原来的值。这种实现技术会有字符的一个回退过程。而在 WPF 中,实现方法不同,直接在 PrevewKeyDown Tunneling 事件中处 理,如果是不需要的字符,把事件设置为已经处理过。这样这个事件就不会进入到后面的 Bubbling 事件 KeyDown 中, WPF 也根本不会显式这个字 符。这种方法的效果将比之前的回退处理好很多。

   
虽然我们可以通过 RoutedEventArgs 参数的 Handled 属性为 True 来终止事件的传递。但是,有时候我们需要某个事件始终被接受处理,这可以通过程序代码实现。使用重载的 AddHanlder 方法。比如,我们给窗口添加一个鼠标右键的处理方法(其中 MRBD_Handler 是类的一个事件 方法):

public AboutDialog() { InitializeComponent(); this.AddHandler(Window.MouseRightButtonDownEvent, new MouseButtonEventHandler(MRBD_Handler), true); }

这样,任何条件下, MRBD_Handler 都可以接收到窗口的鼠标右键事件。即使鼠标右键是点击在窗口中的某个子控件之上。

    Attached
事件

    Attached 属性类似, WPF Element 在事件没有定义的情况下也支持 Tunneling 或者 Bubbling 事件。比如,我们可以在一个简单的窗口程序中这样指定事件函数:

<Window xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation” xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” x:Class=”Window1” Button.Click=”Button_Click” <Button Text="TestButton" Width="50" Height="30"> </Window>

    例子中,因为 Window 本身没有定义 Click 事件,所以我们必须指定 Click 事件属性的名称前缀,也就是定义事件的类名。经过这样的定义后,点击在 Window 中的 TestButton ,也会激发属性声明的 Click 事件,调用对应的 Button_Click 方法。

   
为什么这样的定义可以通过呢?首先编译时, XAML 会看到 Button 类确实定义了一个 Click .NET 事件。在运行时,会直接调用 AddHandler 把这两个事件依附到 Window 对应的类当中。所以上面用 XAML 属性声明的事件代码与下面的程序代码等效:

 

public Window1 { InitializeComponent(); this.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click)); }

你可能感兴趣的:(.net,api,Class,语言,button,WPF)