WPF 路由事件与附加事件

Runtime(运行时)提供两个机制:一个机制能通知发生了紧急事件;另一个机制则规定在发生事件时应该允许什么方法,这正是事件委托的用途。

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

//实例:一个grid中包含一个button,点击button,查看路由事件传递路径
public Window1()
{
    InitializcComponent();
    this.grid.AddHandler(Button.ClickEvent,new RouteEventHandler(this.ButtonClicked));
}
private void ButtonClicked(object sender,RoutedEventArgs e)
{
    MessageBox.Show(e.OriginalSource as FrameworkElement).Name);
}
/*
    因为路由事件时从内部一层一层传递出来到最后到达最外层的grid,并且有grid元素将事件消息传递给ButtonClicked方法来处理,所以传入ButtonClicked方法的参数sender时间上是grid(LogicalTree上的消息源头)而不是点击的button,想要查看事件的源头,则需使用e.OriginalSource(VisualTree上的消息源头).
*/

路由事件定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统来处理。首先使用 RegisterRoutedEvent 方法注册一个 RoutedEvent。按照约定,RoutedEvent 静态字段名称应当以后缀 Event 结束。在本示例中,事件的名称是 Tap,事件的路由策略是 Bubble。在注册调用之后,可以为该事件提供添加和移除公共语言运行时 (CLR) 事件访问器。

public class MyButtonSimple: Button
{
    // Create a custom routed event by first registering a RoutedEventID
    // This event uses the bubbling routing strategy
    public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
        "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

    // Provide CLR accessors for the event
    public event RoutedEventHandler Tap
    {
            add { AddHandler(TapEvent, value); } 
            remove { RemoveHandler(TapEvent, value); }
    }

    // This method raises the Tap event
    void RaiseTapEvent()
    {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(MyButtonSimple.TapEvent);
            RaiseEvent(newEventArgs);
    }
    // For demonstration purposes we raise the event when the MyButtonSimple is clicked
    protected override void OnClick()
    {
        RaiseTapEvent();
    }

}
<Window  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:SDKSample;assembly=SDKSampleLibrary"
    x:Class="SDKSample.RoutedEventCustomApp"

    >
    <Window.Resources>
      <Style TargetType="{x:Type custom:MyButtonSimple}">
        <Setter Property="Height" Value="20"/>
        <Setter Property="Width" Value="250"/>
        <Setter Property="HorizontalAlignment" Value="Left"/>
        <Setter Property="Background" Value="#808080"/>
      </Style>
    </Window.Resources>
    <StackPanel Background="LightGray">
        <custom:MyButtonSimple Name="mybtnsimple" Tap="TapHandler">Click to see Tap custom event work</custom:MyButtonSimple>
    </StackPanel>
</Window>

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

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

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

中断事件路由:所有的路由事件都共享一个公共的事件数据基类 RoutedEventArgs。 RoutedEventArgs 定义了一个采用布尔值的 Handled 属性。 Handled 属性的目的在于,允许路由中的任何事件处理程序通过将 Handled 的值设置为 true 来将路由事件标记为“已处理”。

// StackPanel中添加YesTB TextBox
private void StackPanel_MouseUp(object sender, MouseButtonEventArgs e)
{
       MessageBox.Show("Panel");
}

private void YesTB_MouseUp(object sender, MouseButtonEventArgs e)
{
       MessageBox.Show("button");
       e.Handled = true;
}
//在上面的例子中,将不再触发StackPanel_MouseUp事件。

使用场景:

在我们创建自定义控制的时候,创建一些和业务相关的路由事件,就显得很有必要。

如,创建一个在线考试中的题型展示控件,可以为该控件设计一个自定义事件,为“提交”。这样一来,这个题型控件不仅仅只有一些通用事件,还可以看上去更“业务”。

WPF的事件和C#的事件区别:原文链接:https://blog.csdn.net/it_ziliang/article/details/53885521

1、在定义事件,WPF需要用add和remove来添加和移除与事件数据有关的TapEvent字段。在触发事件时,靠的是控件的单击事件来触发,尽管在实现上与c#有区别,但步骤一样,定义事件,触发事件(区别是c#事件不需要保留数据,因为它只对控件自己负责,WPF的事件要实现路由,要传送)。
        路由事件路由的方式有三种,一种是“冒泡”,就是有事件触发元素往上路由,一种是“直接”,就是只发生在触发元素本身,还有一种是“隧道”,就是从元素树的顶端到触发事件的元素,一般情况下,“隧道”的路由方式的事件,事件的名称前都有一个Preview开头。。路由事件也可以在某个元素上中断它,也就是调用事件订阅方法参的第二个参数,通常是RoutedEventArgs类型,调用它的Handled属性,就会中断事件的路由,到此元素为止。

2、C#中的事件,可以说几乎都是由UI来驱动的,也可以叫做 ”消息驱动“。每次触发事件 都可以看成向程序传递 一条消息,由应用程序的一套算法来 解析消息 来做出反应。 这个过程就是  ”事件订阅“的过程,就是我们在Designer.cs 中常见的  += 符号。

而WPF中的路由事件,从事件的触发者,到事件的响应者 之间  并不存在 ”事件订阅“的关系。事件会通过 冒泡式或者隧道式或者直达式 ”传播“,直到有 ”监听“该事件的监听器监听到,才会做出响应。也就是说, 路由事件的响应者 不是一个,可能是多个。

附加事件:转:https://blog.csdn.net/hyman_c/article/details/52273822

附加事件的本质也是路由事件,路由事件的宿主是Button、Grid等这些我们可以在界面上看得见的控件对象,而附加事件的宿主是Binding类、Mouse类、KeyBoard类这种无法在界面显示的类对象。附加事件的提出就是为了让这种我们无法看见的类也可以通过路由事件同其他类对象进行交流。

/*
Student类定义路由事件,可以看到,附加事件的声明和路由事件完全一样,和路由事件不同的是它并非派生自UIElement,因此没有AddHandler和RemoveHandler两个方法,且没有RaiseEvent这个方法可以激发事件,只能附加到UIElment对象上面进行激发。
*/
public class Student
{
        public static readonly RoutedEvent NameChangedEvent =
            EventManager.RegisterRoutedEvent("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student));
        public int id { get; set; }
        public string name { get; set; }
}

//界面
<Window x:Class="_8_14.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 Name="gridMain">
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="219,266,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
    </Grid>
</Window>

//界面后台处理
public partial class MainWindow : Window
{
        public MainWindow()
        {
            InitializeComponent();

            this.gridMain.AddHandler(Student.NameChangedEvent,
                new RoutedEventHandler(this.StudentNameChangedHandler));
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            Student stu = new Student
            {
                id = 101,
                name = "Tim"
            };
            stu.name = "Tom";
            RoutedEventArgs args = new RoutedEventArgs(Student.NameChangedEvent, stu);
            this.button1.RaiseEvent(args);
        }
        private void StudentNameChangedHandler(object sender, RoutedEventArgs e)
        {
            MessageBox.Show((e.OriginalSource as Student).id.ToString());
        }
}

/*
我们要实现的效果时,在修改Student对象的name属性时,激发Student.NameChangedEvent事件,在界面中设置该附件事件的处理器,弹出被修改姓名的Student对象的id。
button1_Click为界面上按钮的处理事件,在这个事件中我们定义了一个Student的对象,然后修改Student对象的name属性,由于Student对象没有RaiseEvent方法,这里需要借助button1的RaiseEvent方法激发该路由事件。
*/


附加事件宿主不是UIElement的派生类,所以没有RaiseEvent,AddHandler,RemoveHandler这些方法,实际上很少会把附加事件定义在Student这种与业务逻辑相关的类中,一般都是定义在像Binding,Mouse,Keyboard这种全局的Helper类中,当业务逻辑使用Bingding对象与UI元素关联,一旦与业务逻辑相关的对象实现INotifyPropertyChanged接口并将Bingding对象的NotifyOnSourceUpdated属性设为true,则Bingding就会激发器SoureUpdated附加事件,此事件会在UI元素树上路由并被侦听者捕获.

猜你喜欢

转载自blog.csdn.net/lvxingzhe3/article/details/120253586