简书面面观

WPF之路-路由事件

2018-09-14  本文已影响44人  东南有大树

理解路由事件

路由事件是一种可以针对元素树中的多个侦听器而不是仅仅针对引发该事件的对象调用处理程序的事件,也就是说,触发事件源的父级或子级如果都有对该事件的监听,则都能触发事件

路由事件与一般事件的区别在于:路由事件是一种用于元素树的事件,当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,他用一种简单而持久的方式在每个元素上触发,而不需要任何定制的代码(如果用传统的方式实现一个操作,执行整个事件的调用则需要执行代码将事件串联起来)

路由事件的路由策略

所谓的路由策略就是指:路由事件实现遍历元素的方式

路由事件一般使用以下三种路由策略:

  1. 冒泡:由事件源向上传递一直到根元素
  2. 直接:只有事件源才有机会响应事件
  3. 隧道:从元素树的根部调用事件处理程序并依次向下深入直到事件源

一般情况下,WPF提供的输入事件都是以隧道/冒泡对实现的。隧道事件常常被称为Preview事件

冒泡

程序设计思路是,将多个Grid嵌套起来,构成父子结构,在最底层支Grid上定义一个按钮,绑定一个单击事件,并且按钮的所有低级元素都绑定该事件,它们使用同一个事件处理程序;再定义一个ListBox,当事件处理程序执行的时候,打印出事件激发者的名字,这样便能看到事件执行的顺序

下图是界面设计:

下面是XMAL代码,按钮和每一个Grid都绑定了Button.Click="Btn_Click"

<Window x:Class="WPF_CODE.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-->
    <Grid Button.Click="Btn_Click" Name="Grid_1" Background="#FF43AEAE">
        <!--定义两行-->
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        
        <!--第二层Grid-->
        <Grid Button.Click="Btn_Click" Name="Grid_2"  Margin="10" Background="#FF8D888D" Grid.Row="0">
            <!--定义两列-->
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            
            <!--第三层左侧Grid-->
            <Grid Button.Click="Btn_Click" Name="Grid_3_Left" Grid.Column="0" Background="#FF3D8F3A" Margin="10">
                <!--添加一个按钮-->
                <Button Button.Click="Btn_Click" Name="ButtonLeft" Width="80" Height="50" Content="Hello"/>
            </Grid>
            
            <!--第三层右侧Grid-->
            <Grid Button.Click="Btn_Click" Name="Grid_3_Right" Grid.Column="1" Background="#FFC95E3E" Margin="10">
                <!--添加一个按钮-->
                <Button Button.Click="Btn_Click" Name="ButtonRight" Width="80" Height="50" Margin="10" Content="World"></Button>
            </Grid>
            
        </Grid>
        
        <!--定义一个ListBox,用于输出结果-->
        <ListBox Name="Print_List"  Grid.Row="1"/>
        
    </Grid>
    
</Window>

下面是后端代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 WPF_CODE
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        // 事件处理程序
        private void Btn_Click(object sender, RoutedEventArgs e)
        {
            //sender是指由谁激发了这个事件处理程序,它可以获取到触发对象
            //通过(sender as FrameworkElement).Name转换,将触发控件的名称拿出来,
            string message = "触发者:"+(sender as FrameworkElement).Name.ToString();
            this.Print_List.Items.Add(message);

            //e包含了与事件相关的一些参数,e.Handled如果设置为True,则表示冒泡不再继续
            //e.Handled = true;
        }
    }
}

运行程序,点击左侧按钮,结果如下:

点击右侧按钮,结果如下:

可以看出,事件首先在源元素上触发,然后从每一个元素向上沿着树传递,直到到达根元素为止(或者直到处理程序把事件标记为已处理为止),从而调用这些元素中的路由事件

如果将事件处理程序里的e.Handled = true;代码放开,效果如下

//e包含了与事件相关的一些参数,e.Handled如果设置为True,则表示冒泡不再继续
e.Handled = true;

即事件处理程序只要被触发一次,并不会发生冒泡

隧道

隧道的执行顺序与冒泡正好相反,直接看例子,然后再解释,将上便中所有的Click事件改为PreviewMouseDown事件,而绑定的事件处理程序不变

XAML代码如下:

<Window x:Class="WPF_CODE.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-->
    <Grid PreviewMouseDown="Btn_Click" Name="Grid_1" Background="#FF43AEAE">
        <!--定义两行-->
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        
        <!--第二层Grid-->
        <Grid PreviewMouseDown="Btn_Click" Name="Grid_2"  Margin="10" Background="#FF8D888D" Grid.Row="0">
            <!--定义两列-->
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            
            <!--第三层左侧Grid-->
            <Grid PreviewMouseDown="Btn_Click" Name="Grid_3_Left" Grid.Column="0" Background="#FF3D8F3A" Margin="10">
                <!--添加一个按钮-->
                <Button PreviewMouseDown="Btn_Click" Name="ButtonLeft" Width="80" Height="50" Content="Hello"/>
            </Grid>
            
            <!--第三层右侧Grid-->
            <Grid PreviewMouseDown="Btn_Click" Name="Grid_3_Right" Grid.Column="1" Background="#FFC95E3E" Margin="10">
                <!--添加一个按钮-->
                <Button PreviewMouseDown="Btn_Click" Name="ButtonRight" Width="80" Height="50" Margin="10" Content="World"></Button>
            </Grid>
            
        </Grid>
        
        <!--定义一个ListBox,用于输出结果-->
        <ListBox Name="Print_List"  Grid.Row="1"/>
        
    </Grid>
    
</Window>

运行程序,查看效果:

可以看出,隧道是指事件首先是从根元素上被触发,然后从每一个元素向下沿着树传递,直到到达根元素为止(或者直到到达处理程序把事件标记为已处理为止),他的执行方式正好与冒泡策略相反

所有的隧道事件都以Preview开头

后台代码记得将e.Handled=true注释掉

直接策略

事件仅仅在源元素上触发,这个与普通的.Net事件的行为相同,不同的是这样的事件仍然会参与一些路由事件的特定机制,如事件触发器等;该事件唯一可能的处理程序是与其挂接的委托

上一篇下一篇

猜你喜欢

热点阅读