浅谈WPF之路由事件

为了降低由事件订阅带来的耦合度,和代码量,WPF推出了路由事件机制。路由事件与直接事件的区别在于,直接事件激发时,发送者直接将消息通过事件订阅者交给事件响应者,事件响应者对事件的发生做出响应。路由事件的订阅者和响应者之间没有直接显式的订阅关系,事件的拥有者只负责激发事件,事件由谁响应它并不知道,事件响应者通过事件侦听器进行侦听。本文以一些简单的小例子,简述路由事件的基本使用,仅供学习分享使用,如有不足之处,还请指正。

图片

什么是路由事件?

根据MSDN定义:

  • 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。

  • 实现定义:路由事件是由 类的实例支持的 CLR 事件, RoutedEvent 由事件 Windows Presentation Foundation (WPF) 系统处理。

典型的 WPF 应用程序中包含许多元素。无论这些元素是在代码中创建还是在 XAML 中声明,它们存在于彼此关联的元素树关系中。 

路由策略

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

  • Bubbling【冒泡】: 调用事件源上的事件处理程序。路由事件随后会路由到后续的父级元素,直到到达元素树的根。大多数路由事件都使用冒泡路由策略。冒泡路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。

  • Direct【直接】: 只有源元素本身才有机会调用处理程序以进行响应。这类似于窗体用于事件的Windows路由"。但是,与标准 CLR 事件不同,直接路由事件支持类处理 (类处理在即将发布的) 节中进行了说明,并且 和 可以使用 EventSetter或 EventTrigger 。

  • Tunneling【隧道】: 最初将调用元素树的根处的事件处理程序。随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。合成控件的过程中通常会使用或处理隧道路由事件,通过这种方式,可以有意地禁止复合部件中的事件,或者将其替换为特定于整个控件的事件。

冒泡策略

事件的冒泡策略,就像水里的泡泡一样,从底往上,逐级触发。路由事件随后会路由到后续的父级元素,直到到达元素树的根。大多数路由事件都使用冒泡路由策略。冒泡路由事件通常用于报告来自不同控件或其他 UI 元素的输入或状态变化。

图片

路由事件会像泡泡一样,逐层响应,示例如下所示:

图片

 示例源码

<Window x:Class="WpfApp1.OneWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        x:Name="w1"
        Title="OneWindow" Height="350" Width="500" Button.Click="btn1_Click">
    <Grid x:Name="gdOuter" Button.Click="btn1_Click">
        <StackPanel x:Name="sp1" Button.Click="btn1_Click">
            <Grid x:Name="gd1" Button.Click="btn1_Click">
                <Button x:Name="btn1" Content="点我" Click="btn1_Click" Margin="5" Padding="5" FontSize="18"></Button>
            </Grid>
            <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100"  ></RichTextBox>
        </StackPanel>
    </Grid>
</Window>

隧道策略

隧道策略事件,与冒泡策略刚好相反, 最初将调用元素树的根处的事件处理程序。随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。

所有的隧道策略模式事件,都是以Preview开头。隧道事件有时又称作预览事件,这是由该对所使用的命名约定决定的。

隧道策略由顶至下,逐层下探,直至最好一个元素,如下所示:

图片

 示例源码

<Window x:Class="WpfApp1.TwoWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Name="w1"
        Title="TwoWindow" Height="350" Width="500" Button.PreviewMouseDown="btn1_PreviewMouseDown">
    <Grid>
        <Grid x:Name="gdOuter" Button.PreviewMouseDown="btn1_PreviewMouseDown">
            <StackPanel x:Name="sp1" Button.PreviewMouseDown="btn1_PreviewMouseDown">
                <Grid x:Name="gd1" Button.PreviewMouseDown="btn1_PreviewMouseDown">
                    <Button x:Name="btn1" Content="点我" PreviewMouseDown="btn1_PreviewMouseDown" Margin="5" Padding="5" FontSize="18"></Button>
                </Grid>
                <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100"  ></RichTextBox>
            </StackPanel>
        </Grid>
    </Grid>
</Window>

注意:在 WPF 中提供的输入事件通常是以隧道/浮升对实现的

事件阻止

在实际应用中,如果不想事件采用冒泡或隧道策略,向上或向下执行,则需要设置e.Handled=true即可,如下所示:

图片

 示例源码:


private void btn1_Click(object sender, RoutedEventArgs e)
{
      this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource));
      e.Handled = true;
}

后台添加路由事件

路由事件既可以通过XAML的方式,进行设置,也可以通过后台代码的方式进行设置,如下所示:

图片

 后台设置路由事件,如下所示:

/// <summary>
/// ThreeWindow.xaml 的交互逻辑
/// </summary>
public partial class ThreeWindow : Window
{
   public ThreeWindow()
   {
        InitializeComponent();
        this.btn1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
        this.gd1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
        this.sp1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
        this.gdOuter.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
        this.w1.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn1_Click));
    }


    private void btn1_Click(object sender, RoutedEventArgs e)
    {
        this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource));
        //e.Handled = true;
    }
}

自定义路由事件

创建自定义路由事件,步骤如下:

  1. 声明并注册路由事件。

  2. 为路由事件添加CLR事件包装器。

  3. 创建可以激发事件的方法。

具体操作步骤如下:

首先创建的自定义控件,继承自Button按钮,如下所示:

namespace WpfApp1
{
    /// <summary>
    /// 自定义路由事件
    /// </summary>
    public class TimeButton:Button
    {
        /// <summary>
        /// 声明和注册路由事件
        /// </summary>
        public static readonly RoutedEvent TimeEvent = EventManager.RegisterRoutedEvent("Time", RoutingStrategy.Bubble, typeof(EventHandler<TimeEventArgs>), typeof(TimeButton));

        /// <summary>
        /// 事件包装器
        /// </summary>
        public event RoutedEventHandler Time{
            add { this.AddHandler(TimeEvent, value); }
            remove
            {
                this.RemoveHandler(TimeEvent, value);
            }
        }

        /// <summary>
        /// 重写方法,激发事件
        /// </summary>
        protected override void OnClick()
        {
            base.OnClick();
            TimeEventArgs e = new TimeEventArgs(TimeEvent,this);
            e.ClickTime = DateTime.Now;
            this.RaiseEvent(e);
        }

    }

    public class TimeEventArgs : RoutedEventArgs {
        public TimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) {

        }

        public DateTime ClickTime { get; set; }
    }
}

在窗体中,创建自定义按钮实例。如下所示:

<Window x:Class="WpfApp1.A1Window"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="A1Window" Height="450" Width="800">
    <Grid x:Name="gd1" local:TimeButton.Time="timeButton1_Time">
        <StackPanel x:Name="sp1" local:TimeButton.Time="timeButton1_Time">
            <local:TimeButton x:Name="timeButton1" Content="报时" Time="timeButton1_Time"></local:TimeButton>
            <RichTextBox x:Name="txtInfo" Margin="5" MinHeight="100"  ></RichTextBox>
        </StackPanel>
    </Grid>
</Window>

然后实现事件函数,如下所示:

private void timeButton1_Time(object sender, TimeEventArgs e)
{
      this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件时间为:{1}\r\n", (sender as FrameworkElement).Name, e.ClickTime.ToString("yyyy-MM-dd hh:mm:ss.fff")));
}

自定义路由事件,示例截图如下:

图片

以上就是【浅谈WPF之路由事件】的全部内容,关于更多详细内容,可参考官方文档。希望能够一起学习,共同进步。

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
WPF中,自定义路由事件可以通过以下步骤实现: 1. 定义一个自定义路由事件: ``` public class MyRoutedEvent : RoutedEventArgs { public MyRoutedEvent() : base() { } public MyRoutedEvent(RoutedEvent routedEvent) : base(routedEvent) { } public MyRoutedEvent(RoutedEvent routedEvent, object source) : base(routedEvent, source) { } public string MyEventArgs { get; set; } // 自定义事件参数 public static readonly RoutedEvent MyEvent = EventManager.RegisterRoutedEvent("MyEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyRoutedEvent)); // 添加路由事件处理程序 public event RoutedEventHandler MyEventHandler { add { AddHandler(MyEvent, value); } remove { RemoveHandler(MyEvent, value); } } // 触发路由事件 public void RaiseMyEvent() { RaiseEvent(new RoutedEventArgs(MyEvent)); } } ``` 在这个例子中,我们定义了一个继承自RoutedEventArgs的类,并添加了一个自定义事件参数MyEventArgs。我们还定义了一个静态只读的MyEvent路由事件,并为它添加了一个路由事件处理程序MyEventHandler。最后,我们实现了一个RaiseMyEvent方法,该方法将触发MyEvent路由事件。 2. 在UI元素中使用自定义路由事件: ``` <Button Content="Click me" Click="Button_Click"/> ``` 在这个例子中,我们将按钮的Click事件绑定到Button_Click方法。在该方法中,我们可以创建一个MyRoutedEvent实例并触发它: ``` private void Button_Click(object sender, RoutedEventArgs e) { MyRoutedEvent myEvent = new MyRoutedEvent(); myEvent.MyEventArgs = "Hello World!"; RaiseEvent(myEvent); } ``` 在这个例子中,我们创建了一个MyRoutedEvent实例,并将MyEventArgs设置为“Hello World!”。然后,我们调用RaiseEvent方法触发MyEvent路由事件。 3. 在父控件中处理自定义路由事件: ``` <Grid local:MyRoutedEvent.MyEvent="Grid_MyEvent"> <!-- 子控件 --> </Grid> ``` 在这个例子中,我们将Grid控件的MyEvent路由事件绑定到Grid_MyEvent方法。在该方法中,我们可以获取到MyEventArgs的值: ``` private void Grid_MyEvent(object sender, RoutedEventArgs e) { MyRoutedEvent myEvent = (MyRoutedEvent)e; string myEventArgs = myEvent.MyEventArgs; // 处理自定义路由事件 } ``` 在这个例子中,我们获取MyRoutedEvent实例,并将其转换为MyRoutedEvent类型。然后,我们可以获取MyEventArgs的值并进行处理。 这就是一个简单的自定义路由事件的实现方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老码识途呀

写作不易,多谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值