WPF 路由事件

Posted 无熵~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF 路由事件相关的知识,希望对你有一定的参考价值。

路由和事件定义和理解:

        事件及消息,Windows通过消息传递信息。路由是起点到终点间有若干个中转站,从起点出发后经过每个中转站时要做出选择,最终以正确的路径到达终点。

        WPF的UI是由布局组件和控件构成的树形结构,当这颗树的某个节点激发出某个事件时,事件就会在UI组件树沿着一定方向传递下去,或者被处理(e.Handled = true)。WPF由两棵树组成:Logical Tree(逻辑树,布局组件和控件构成)和Visual Tree(Logical Tree延申到Template组件级别,及控件细节)组成。路由事件是沿着Visual Tree传递的,这样才让"藏"在Template里的控件把消息送出来(Source源头是VisualTree的消息源头,OriginalSource是LogicTree的消息源头)。

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

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

  • Bubble(冒泡模式):最初调用事件源上的事件处理程序, 然后路由事件路由到连续的父元素,依次调用其事件处理程序,直到它到达元素树根。 大多数路由事件都使用浮升路由策略。 
  • Tunnel(隧道模式):最初调用元素树根上的事件处理程序, 然后路由事件路由到连续的子元素,依次调用其事件处理程序,直到到达事件源。 遵循隧道路由的事件也称为 预览 事件, WPF 输入事件通常实现为 预览和浮升对。
  • Direct(直接模式):仅调用事件源上的事件处理程序。 这种非路由策略类似于Windows 窗体 UI 框架事件,即标准 CLR 事件。 与 CLR 事件不同,直接路由事件支持 类处理 ,可由 EventSetters 和 EventTrigger 使用。

路由事件使用:

        这里有三个按键,这三个按钮中的每一个都是潜在的 Click 事件源。 单击其中一个按钮时,它会引发 Click 从按钮向上气泡到根元素的事件。 Button和 Border 元素没有附加事件处理程序,但 StackPanel 附加了事件处理程序。 树中较高、未显示的其他元素可能也 Click 附加了事件处理程序。 Click当事件到达 元素时StackPanel,WPF 事件系统会调用YesNoCancelButton_Click附加到它的处理程序。 示例中事件的事件路由 Click 为: Button ->StackPanel ->Border -> 连续的父元素。

前端代码:

<Border Height="30" Width="200" BorderBrush="Gray" BorderThickness="1">
    <StackPanel Background="LightBlue" Orientation="Horizontal" Button.Click="YesNoCancelButton_Click">
        <Button Name="YesButton">Yes</Button>
        <Button Name="NoButton">No</Button>
        <Button Name="CancelButton">Cancel</Button>
    </StackPanel>
</Border>

后端代码:

 private void YesNoCancelButton_Click(object sender, RoutedEventArgs e)
 
     //通过Source查询事件源
     FrameworkElement sourceFrameworkElement = e.Source as FrameworkElement;
     MessageBox.Show(sourceFrameworkElement.Name);
     //事件被处理,不在继续传递    
     e.Handled = true;
 

自定义路由事件:

创建自定义路由事件可分为三个步骤:

  1. 声明并注册路由事件
  2. 为路由事件添加CLR事件包装
  3. 创建可以激发路由事件的方法

        创建一个继承至RoutedEventArgs类的路由事件参数类,以便传递给事件响应着一些参数信息;如果我们没有什么其他额外信息需要传递的话,我们可以直接使用RoutedEventArgs这个路由事件参数基类。

public class ReportTimeEventArgs:RoutedEventArgs
    
        public DateTime ClickTime  get; set; 
        public ReportTimeEventArgs(RoutedEvent routedEvent, object source)
            : base(routedEvent, source)  
    

注册路由事件,并且添加普通事件包装器和事件激发方法。

    /// <summary>
    /// 创建一个报时按钮
    /// </summary>
   public class TimeButton:Button
    
        //声明和注册路由事件
        public static readonly RoutedEvent ReportTimeEvent=         EventManager.RegisterRoutedEvent("ReportTime",RoutingStrategy.Bubble,typeof(EventHandler<ReportTimeEventArgs>),typeof(TimeButton));
        //普通事件包装器
        public event RoutedEventHandler ReportTime
        
            addthis.AddHandler(ReportTimeEvent,value);
            removethis.RemoveHandler(ReportTimeEvent,value);
        
        //激发路由事件,借用Click事件的激发方法
        protected override void OnClick()
        
             base.OnClick();
            ReportTimeEventArgs args=new ReportTimeEventArgs (ReportTimeEvent,this);//这个事件参数初始化的时候包含的参数提供了事件的来源
            args.ClickTime=DateTime.Now;
            this.RaiseEvent(args);//激发事件,这里和普通事件不同(普通事件激发是有事件包装器激发的)
        
    

程序的界面XAML代码:

<Window Name="wimdow1">
   <Grid x:Name="grid1"  local:TimeButton.ReportTime="ReportTimeHandler">
        <Grid x:Name="grid2"  local:TimeButton.ReportTime="ReportTimeHandler">
            <Grid x:Name="grid3"  local:TimeButton.ReportTime="ReportTimeHandler">
                <StackPanel x:Name="stackPanel"  local:TimeButton.ReportTime="ReportTimeHandler">
                    <ListBox x:Name="listBox"/>
                    <local:TimeButton x:Name="timeButton" Width="80" Height="80" Content="报时" local:TimeButton.ReportTime="ReportTimeHandler" ></local:TimeButton>
                </StackPanel>
            </Grid>
        </Grid>
    </Grid>
</Window>

后台代码:

//ReportTimeEvent路由事件处理器
private void ReportTimeHandler(object sender,ReportTimeEventArgs e)

    FrameworkElement element =sender as FrameworkElement;
    string timeStr=e.ClickTime.ToLongTimeString();
    string content=stirng.Format("0到达1",timeStr,element.Name);
    this.ListBox.Items.Add(content);

Note:

  • 路由事件注册方法RegisterRoutedEvent方法和注册依赖属性的方法很类似,他需要4个参数,普通事件包装器的名字、路由事件策略、事件处理类型、事件拥有者;
  • 事件包装器,这里的目的主要是为了适应以前学习.net同学的方便这样我们就可以使用+=或者-=,我们其实可以直接调用AddHandler或者RemoveHandler方法来添加和删除事件;
  • 激发事件方法调用的是RaiseEvent(),这个方法是在UIElement类中定义的,即所有UI元素都有这个方法。

wpf路由事件实例:https://download.csdn.net/download/lvxingzhe3/87642546

WPF Demo18 路由事件

using System.Windows;

namespace 路由事件2
{
    public class Student
    {
        ////声明并定义路由事件
        //public static readonly RoutedEvent NameChangedEvent =
        //    EventManager.RegisterRoutedEvent("NameChanged",
        //    RoutingStrategy.Bubble,
        //    typeof(RoutedEventHandler), 
        //    typeof(Student));

        private int id;

        public int Id
        {
            get { return id; }
            set { id = value; }
        }
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    }
}
<Window x:Class="路由事件2.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="testGrid">
        <Button x:Name="btnTest"  Content="ok" Width="80"  Height="75" FontSize="18" Click="btnTest_Click"/>
    </Grid>
</Window>
using System.Windows;

namespace 路由事件2
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        //声明并定义路由事件
        public static readonly RoutedEvent NameChangedEvent =
            EventManager.RegisterRoutedEvent("NameChanged",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(MainWindow));

        public MainWindow()
        {
            InitializeComponent();

            //为grid添加路由事件侦听器
            this.testGrid.AddHandler(NameChangedEvent, new RoutedEventHandler(StudentNameChangeEvent));
        }

        private void btnTest_Click(object sender, RoutedEventArgs e)
        {
            Student stu = new Student()
            {
                Id = 1,
                Name = "name001"
            };

            stu.Name = "name007";

            //准备事件消息并发送路由事件
            RoutedEventArgs arg = new RoutedEventArgs(NameChangedEvent, stu);
            //RaiseEvent用于触发路由事件
            this.btnTest.RaiseEvent(arg);
        }

        public void StudentNameChangeEvent(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Id==" + (e.OriginalSource as Student).Id.ToString()
                + "\\n"
                + "name==" + (e.OriginalSource as Student).Name.ToString());
        }
    }
}

技术分享

 

实例二:

<Window x:Class="路由事件3.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="testGrid">
        <Button x:Name="btnTest" Content="ok" Width="80" Height="75" FontSize="18" Click="btnTest_Click"/>
    </Grid>
</Window>
using System.Windows;

namespace 路由事件3
{
    public class Student
    {
        //声明并定义路由事件
        public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
            ("NameChange",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(Student));

        //为界面元素添加路由侦听器
        public static void AddNameChangedHandler(DependencyObject d,RoutedEventHandler h) 
        {
            UIElement e = d as UIElement;
            if (e != null) e.AddHandler(Student.NameChangedEvent, h);
        }

        //移除侦听
        public static void RemoveNameChangedHandler(DependencyObject d, RoutedEventHandler h)
        {
            UIElement e = d as UIElement;
            if (e != null) e.RemoveHandler(Student.NameChangedEvent, h);
        }

        private int id;
        public int Id
        {
            get { return id; }
            set { id = value; }
        }
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    }
}


using System.Windows;

namespace 路由事件3
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //为外层Grid添加路由事件侦听器
            Student.AddNameChangedHandler(this.testGrid,new RoutedEventHandler(NameChangedEvent));
        }

        public void NameChangedEvent(object sender,RoutedEventArgs e)
        {
            MessageBox.Show("Id==" + (e.OriginalSource as Student).Id.ToString()
               + "\\n"
               + "name==" + (e.OriginalSource as Student).Name.ToString());
        }

        private void btnTest_Click(object sender, RoutedEventArgs e)
        {
            Student stu = new Student()
            {
                Id = 1,
                Name = "001"
            };

            stu.Name = "002";

            //准备事件消息并发送路由事件
            RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent, stu);
            this.btnTest.RaiseEvent(arg);
        }
    }
}

技术分享

 

以上是关于WPF 路由事件的主要内容,如果未能解决你的问题,请参考以下文章

WPF的路由事件冒泡事件隧道事件(预览事件)

WPF--路由事件

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

WPF事件内置路由事件

WPF快速入门系列——深入解析WPF事件机制

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