WPF中的路由事件

为了降低由事件订阅带来的耦合度和代码量,WPF推出了路由事件机制。路由事件与直接事件的区别在于:

直接事件激发时,发送者直接将消息通过事件订阅交送给事件响应者,事件响应者使用其事件处理方法对事件的发生作出响应、驱动程序逻辑按客户需求运行;

路由事件的事件拥有者和事件响应者之间则没有直接显示的订阅关系,事件的拥有者只负责激发事件,事件将由谁响应它并不知道,事件的相应者则安装有事件侦听器,针对某类事件进行侦听,当有此类事件传递至此时,事件相应者就使用事件处理器相应事件并决定事件是否可以继续传递。

这里要说明一下WPF中的两种”树“:一种叫逻辑树”Logical Tree“;一种叫可视元素树”Visual Tree“。所有属性结构都是LogicalTree,LogicalTree的特点是它完全由布局组件和控件构成(包括列表类控件中的条目元素),换句话说就是它的每个结点不是布局组件就是控件。在WPF的LogicalTree上,充当叶子的一般都是控件,如果我们把WPF的控件也放大去观察,会发现每个WPF控件本身也是一颗由更细微级别的组件(它们不是控件,而是一些可视化组件,派生自Visual类)组成的树。如果把LogicalTree延伸至Template组件级别,我们得到的就是VisualTree,也就是树的叶子。当一个路由事件被激发后是沿着VisualTree传递的

举个例子:在VisualTree上有一个Button控件,当它被单击后就相当于喊了一声“我被单击了”,这样一个Button.Click事件就开始在Visual Tree传播,当事件经过某个结点时如果这个结点没有安装用于侦听Button.Click事件的“耳朵",那么它会无视这个事件,让它畅通无助的继续传播,如果某个结点安装了针对Button.Click的侦听器,它的时间处理器就会被调用(侦听者并不关心具体哪个Button的Click事件被传来,即任何一个传来的Button.Click事件都会被侦听到),在事件处理器内部程序员可以查看路由事件的出发点是哪个控件、上一站是哪里,还可以决定事件传递到此为止还是可以继续传递——路由事件就是这样依靠”口耳相传“的办法将消息传给”关心“它的控件。

尽管WPF推出了路由事件机制,但它仍然支持传统的直接事件模型。

<Window x:Class="Chapter8.Page161.RoutedEvent.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="gridRoot" Background="LightBlue">
        <Grid x:Name="gridA" Margin="10" Background="Blue">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="10">
                <Button x:Name="buttonLeft" Content="Left" Width="40" Height="100" Margin="10"/>
            </Canvas>
            <Canvas x:Name="canvasRight" Grid.Column="1" Background="Yellow" Margin="10">
                <Button x:Name="buttonRight" Content="Right" Width="40" Height="100" Margin="10"/>
            </Canvas>
        </Grid>
    </Grid>
</Window>

当单机buttonLeft时,Button.Click事件就会沿着buttonLeft——canvasLeft——gridA——gridRoot——Window这条路线向上传送;单机buttonRight,则Button.Click事件沿着buttonRight——canvasRight——gridA——gridRoot——Window路线传送。因为目前还没有哪个控件侦听Button.Click事件,所以单机按钮后尽管事件向上传递却并没有受到响应。下面我们为gridRoot安装针对Button.Click事件的侦听器。方法很简单,就是在窗体的构造器中调用gridRoot的AddHandler方法把想侦听的事件与事件的处理器关联起来:

        public MainWindow()
        {
            InitializeComponent();
            //把想要监听的Button的ClickEvent事件与事件处理器RoutedEventHandler委托方法关联起来
            this.gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonClicked));
        }

AddHandler方法源自UIElement类,也就是说,所有UI控件都具有这个方法。书写AddHandler方法时它的第一个参数是Button.ClickEvent而不是Button.Click。原来,WPF事件系统也使用了与属性系统类似的”静态字段——包装器“的策略。也就是说,路由事件本身是一个RoutedEvent类型的静态成员变量(Button.ClickEvent),Button还有一个与之对应的Click事件(CLR包装)专门用于对外暴露这个事件。”名字叫路由时间,可我们却选择一个静态字段“。

上面的代码让最外层的Grid(gridRoot)能够捕捉到从内部”飘“出来的按钮单机事件,捕捉到后会用this.ButtonClicked方法来进行相应处理。

        public void ButtonClicked(object sender, RoutedEventArgs e)
        {
            MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
        }

这里有一点非常重要:因为路由事件(的消息)是从内部一层层传递出来最后到达最外层的gridRoot,并且由gridRoot元素将事件消息交给ButtonClicked方法来处理,所以传入 ButtonClicked方法的参数sender实际上是gridRoot而不是被单击的Button,这与传统的直接事件不太一样。为了查看事件的源头需要使用e.OriginalSource,使用它的时候需要使用as/is操作符或者强制转换类型把它识别/转换为正确的类型。

在XAML里可以写成这样:

<Grid x:Name="gridRoot" Background="LightBlue" Button.Click="ButtonClicked">


路由事件是沿着VisualTree传递的。VisualTree和LogicalTree的区别就在于:LogicalTree的叶子结点是构成用户界面的控件,而VisualTree要连控件中的细微结构也算上。

我们说的”路由事件在VisualTree上传递“,本意上是说”路由事件的消息在VisualTree上传递“,而路由事件的消息则包含在RoutedEventArgs实例中。RoutedEventArgs有两个属性Source和OriginalSource,这两个属性都表示路由事件传递的起点(即事件消息的源头),只不过Source表示的是LogicalTree上的消息源头,而OriginalSource则表示VisualTree上的源头

首先创建一个UserControl:

<UserControl x:Class="Chapter8.Page169.RouteEventSource.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="5">
        <Button x:Name="innerButton" Width="80" Height="80" Content="OK"/>
    </Border>
</UserControl>

这个UserControl的类名为MyUserControl,其中包含一个名为innerButton的Button。然后把这个UserControl添加到主窗体中,注意这里添加UserControl只需要在主窗体中添加命名空间后,直接使用即可,不需要放到资源标签中使用。

<Window x:Class="Chapter8.Page169.RouteEventSource.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Chapter8.Page169.RouteEventSource"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:MyUserControl x:Name="myUserControl" Margin="10"/>
    </Grid>
</Window>

最后在后台代码为主窗体添加对Button.Click路由事件的侦听:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Chapter8.Page169.RouteEventSource
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //为主窗体添加对Button.Click事件的侦听
            this.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.Button_Click));
        }

        //路由事件处理器
        public void Button_Click(object sender, RoutedEventArgs e)
        {
            //VisualTree的Source——OriginalSource
            string strOriginalSource = (e.OriginalSource as FrameworkElement).Name;
            //LogicalTree的Source
            string strSource = (e.Source as FrameworkElement).Name;

            MessageBox.Show("strOriginalSource=" + strOriginalSource + "\r\n" + "strSource=" + strSource);
        }
    }
}

Button.Click路由事件是从MyUserControl的innerButton发出来的,在主窗体中,myUserControl是LogicalTree的末端结点,所以e.Source就是myUserControl;而窗体的VisualTree则包含了myUserControl的内部结构,可以”看见“路由事件究竟是从哪个控件发出来的,所以使用e.OriginalSource可获得innerButton。




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值