WPF 理解事件路由

时间:2021-08-01 00:05:37

路由事件的概念
  对于标准的 .NET 时间来说,时间可以与一个或者多个元素相关联, 但是每个要关联的元素需要显示进行订阅,

  否则 .NET 将忽视该对象, WPF 中的路由事件使用了一种不同的机制,

  事件可以在 WPF 的元素树向上或者向下进行传递,无论是否显示地关联, 位于元素树上下级的元素都有机会处理事件.

EXAMPLE:
XAML

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

BACKGROUND CODE

 

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}

 

路由策略

路由事件使用以下三个路由策略之一:
  ● Bubbling 冒泡:

      针对事件源调用事件处理程序。 路由事件随后会路由到后续的父元素,直到到达元素树的根。

      大多数路由事件都使用冒泡路由策略。冒泡路由事件通常用来报告来自不同控件或其他 UI 元素的输入或状态变化。
  ● Direct 直接:

      只有源元素本身才有机会调用处理程序以进行响应。 这与 Windows 窗体用于事件的“路由”相似。

      但是,与标准 CLR 事件不同的是,直接路由事件支持类处理(类处理将在下一节中介绍)而且可以由 EventSetter 和 EventTrigger 使用。
  ● Tunneling 隧道:

      最初将在元素树的根处调用事件处理程序。

      随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)方向,沿路由线路传播到后续的子元素。

      在合成控件的过程中通常会使用或处理隧道路由事件,这样,就可以有意地禁止显示复合部件中的事件,或者将其替换为特定于整个控件的事件。

      在 WPF 中提供的输入事件通常是以隧道/冒泡对实现的。 隧道事件有时又称作 Preview 事件,这是由隧道/冒泡对所使用的命名约定决定的。

EXAMPLE:
XAML

<Window x:Class="WPFlayout.ContentControl.UnderstandRoutedEvent"
        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:WPFlayout.ContentControl"
        mc:Ignorable="d"
        Title="UnderstandRoutedEvent" Height="300" Width="300"
        ButtonBase.Click="button1_Click">
    <!--为Window和Grid分别关联ButtonBase的Click事件-->
    <Grid Name="Grid" ButtonBase.Click="button1_Click">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBox Name="textBox1" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" />
        <!--在按钮的Parent中添加ButtonBase的Click事件关联-->
        <StackPanel Grid.Row="1" Orientation="Horizontal" Name="stk" ButtonBase.Click="button1_Click">
            <!--为按钮指定单击事件-->
            <Button  Height="23" Name="button1" Click="button1_Click">
                <Button.Content>
                    <StackPanel Orientation="Horizontal">
                        <!--<Image Source="" Name="img"></Image>-->
                        <TextBlock Text="点击触发单击事件" Name="txt"></TextBlock>
                    </StackPanel>
                </Button.Content>
            </Button>
            <Button Name="button2" Click="button1_Click" Margin="10,0,0,0">点击触发单击事件</Button>
        </StackPanel>
    </Grid>
</Window>

BACKGROUND CODE

namespace WPFlayout.ContentControl
{
    /// <summary>
    /// UnderstandRoutedEvent.xaml 的交互逻辑
    /// </summary>
    public partial class UnderstandRoutedEvent : Window
    {
        public UnderstandRoutedEvent()
        {
            InitializeComponent();
        }
 
        int i = 0;
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            ++i;
            StringBuilder eventstr = new StringBuilder();
            //获取触发事件的元素
            FrameworkElement fe = (FrameworkElement)sender;
            //显示触发的次序
            eventstr.Append("触发时次序:" + i.ToString() + "\n");
            eventstr.Append("触发事件的元素名:");
            eventstr.Append(fe.Name);
            eventstr.Append("\n");
            //获取事件源,也就是是由哪个元素所引发的事件。
            FrameworkElement fe2 = (FrameworkElement)e.Source;
            eventstr.Append("事件源类型:");
            eventstr.Append(e.Source.GetType().ToString());
            eventstr.Append("\n");
            eventstr.Append(" 名称:");
            eventstr.Append(fe2.Name);
            eventstr.Append("\n");
            //获知事件传递的方法
            eventstr.Append("路由策略:");
            eventstr.Append(e.RoutedEvent.RoutingStrategy);
            eventstr.Append("\n");
            textBox1.Text += eventstr.ToString();
 
            textBox1.ScrollToEnd();
        }
    }
}

illustrate

WPF 理解事件路由

 

 

附加事件
在 WPF 中, 还可以定义与附加属性类似的附加事件. 比如, Button 控件的 Click 事件,

很多控件并没有定义这个事件. 附加事件可以像在附加属性一样, 为一个类指定在另外一个类中定义的事件.

注意:
  Button 类的 Click 事件定义在基类 ButtonBase 中,但是 ButtonBase 是所有按钮控件的基类,

  包括 RadioButton, CheckBox etc. 如果只想处理 Button 的单击事件, 则使用 Button.Click

EXAMPLE:

<Window x:Class="WPFlayout.ContentControl.AttachedEvents"
        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:WPFlayout.ContentControl"
        mc:Ignorable="d"
        Title="AttachedEvents" Height="300" Width="300">
    <StackPanel Name="stackpanel">
        <Button Name="btn1">按钮1</Button>
        <Button Name="btn2">按钮2</Button>
        <Button Name="btn3">按钮3</Button>
        <Button Name="btn4">按钮4</Button>
        <Button Name="btn5">按钮5</Button>
    </StackPanel>
</Window>

BACKGROUND CODE

注意:
  附加事件的代码定义与标准 .NET 事件定义有些不用,

  不能使用 +=, -= 来附加或者移除事件, 必须调用 AddHandler 方法,该方法定义在 UIElement 类中.

namespace WPFlayout.ContentControl
{
    /// <summary>
    /// AttachedEvents.xaml 的交互逻辑
    /// </summary>
    public partial class AttachedEvents : Window
    {
        public AttachedEvents()
        {
            InitializeComponent();
 
            //使用AddHandler方法关联附加事件
            stackpanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(OnClick));
        }
 
        private void OnClick(object sender, RoutedEventArgs e)
        {
            if (e.Source is Button)
            {
                MessageBox.Show(((Button)e.Source).Content.ToString());
            }
        }
    }
}