WPF - 路由事件


路由事件是一种事件类型,它可以调用元素树中多个侦听器上的处理程序,而不仅仅是引发事件的对象。它基本上是一个由路由事件类的实例支持的 CLR 事件。它已在 WPF 事件系统中注册。RoutedEvents 具有三种主要路由策略,如下所示 -

  • 直接事件
  • 冒泡事件
  • 隧道事件

直接事件

直接事件类似于 Windows 窗体中的事件,由事件发起的元素引发。

与标准 CLR 事件不同,直接路由事件支持类处理,并且可以在您的自定义控件样式中的事件设置器和事件触发器中使用它们。

直接事件的一个很好的例子是 MouseEnter 事件。

冒泡事件

冒泡事件从事件发起的元素开始。然后,它沿着视觉树向上移动到视觉树中的最顶层元素。因此,在 WPF 中,最顶层的元素很可能是一个窗口。

隧道事件

调用元素树根上的事件处理程序,然后事件沿着可视树向下传播到所有子节点,直到到达事件起源的元素。

冒泡事件和隧道事件之间的区别在于隧道事件始终以预览开始。

在 WPF 应用程序中,事件通常作为隧道/冒泡对实现。因此,您将有一个 MouseDown 预览,然后是一个 MouseDown 事件。

下面给出了一个路由事件的简单示例,其中使用一些属性和事件创建了一个按钮和三个文本块。

<Window x:Class = "WPFRoutedEvents.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   Title = "MainWindow" Height = "450" Width = "604" ButtonBase.Click  = "Window_Click" >
	
   <Grid> 
      <StackPanel Margin = "20" ButtonBase.Click = "StackPanel_Click">
		
         <StackPanel Margin = "10"> 
            <TextBlock Name = "txt1" FontSize = "18" Margin = "5" Text = "This is a TextBlock 1" /> 
            <TextBlock Name = "txt2" FontSize = "18" Margin = "5" Text = "This is a TextBlock 2" /> 
            <TextBlock Name = "txt3" FontSize = "18" Margin = "5" Text = "This is a TextBlock 3" /> 
         </StackPanel> 
			
         <Button Margin = "10" Content = "Click me" Click = "Button_Click" Width = "80"/> 
      </StackPanel> 
   </Grid> 
	
</Window>

以下是 Button、StackPanel 和 Window 的 Click 事件实现的 C# 代码。

using System.Windows; 
 
namespace WPFRoutedEvents { 
   /// <summary> 
      /// Interaction logic for MainWindow.xaml 
   /// </summary>
	
   public partial class MainWindow : Window { 
	
      public MainWindow() { 
         InitializeComponent(); 
      }  
		
      private void Button_Click(object sender, RoutedEventArgs e) { 
         txt1.Text = "Button is Clicked"; 
      } 
		
      private void StackPanel_Click(object sender, RoutedEventArgs e) { 
         txt2.Text = "Click event is bubbled to Stack Panel"; 
      } 
		
      private void Window_Click(object sender, RoutedEventArgs e) { 
         txt3.Text = "Click event is bubbled to Window"; 
      }
		
   } 
}

当您编译并执行上述代码时,它将产生以下窗口 -

路由事件

当您单击该按钮时,文本块将更新,如下所示。

单击按钮

如果您想在任何特定级别停止路由事件,则需要设置 e.Handled = true;

让我们更改StackPanel_Click事件,如下所示 -

private void StackPanel_Click(object sender, RoutedEventArgs e) { 
   txt2.Text = "Click event is bubbled to Stack Panel"; 
   e.Handled = true; 
}

当您单击按钮时,您将观察到单击事件不会被路由到窗口,而是会停在堆栈面板上,并且第三个文本块不会更新。

点击事件

自定义路由事件

在.NET框架中,还可以定义自定义路由事件。您需要按照下面给出的步骤在 C# 中定义自定义路由事件。

  • 使用系统调用 RegisterRoatedEvent 声明并注册您的路由事件。

  • 指定路由策略,即 Bubble、Tunnel 或 Direct。

  • 提供事件处理程序。

让我们通过一个示例来了解有关自定义路由事件的更多信息。请按照以下步骤操作 -

  • 使用 WPFCustomRoatedEvent 创建新的 WPF 项目

  • 右键单击您的解决方案并选择“添加”>“新项目...”

  • 将打开以下对话框,现在选择自定义控件 (WPF)并将其命名为MyCustomControl

自定义路由事件
  • 单击“添加”按钮,您将看到两个新文件(Themes/Generic.xaml 和 MyCustomControl.cs)将添加到您的解决方案中。

以下 XAML 代码在 Generic.xaml 文件中设置自定义控件的样式。

<ResourceDictionary 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:local = "clr-namespace:WPFCustomRoutedEvent">
	
   <Style TargetType = "{x:Type local:MyCustomControl}"> 
      <Setter Property = "Margin" Value = "50"/> 
      <Setter Property = "Template"> 
         <Setter.Value> 
            <ControlTemplate TargetType = "{x:Type local:MyCustomControl}">
				
               <Border Background = "{TemplateBinding Background}" 
                  BorderBrush = "{TemplateBinding BorderBrush}" 
                  BorderThickness = "{TemplateBinding BorderThickness}"> 
                  <Button x:Name = "PART_Button" Content = "Click Me" /> 
               </Border> 
					
            </ControlTemplate> 
         </Setter.Value> 
      </Setter> 
   </Style> 
	
</ResourceDictionary>

下面给出的是MyCustomControl 类的 C# 代码,该类继承自Control 类,在该类中为自定义控件创建了自定义路由事件 Click。

using System.Windows; 
using System.Windows.Controls;  

namespace WPFCustomRoutedEvent { 

   public class MyCustomControl : Control { 
	
      static MyCustomControl() { 
         DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), 
            new FrameworkPropertyMetadata(typeof(MyCustomControl))); 
      } 
		
      public override void OnApplyTemplate() { 
         base.OnApplyTemplate();
			
         //demo purpose only, check for previous instances and remove the handler first 
         var button  =  GetTemplateChild("PART_Button") as Button; 
         if (button ! =  null) 
         button.Click + =  Button_Click;  
      } 
		
      void Button_Click(object sender, RoutedEventArgs e) { 
         RaiseClickEvent(); 
      } 
		
      public static readonly RoutedEvent ClickEvent  =  
         EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, 
         typeof(RoutedEventHandler), typeof(MyCustomControl)); 
			
      public event RoutedEventHandler Click { 
         add { AddHandler(ClickEvent, value); } 
         remove { RemoveHandler(ClickEvent, value); } 
      } 
		
      protected virtual void RaiseClickEvent() { 
         RoutedEventArgs args = new RoutedEventArgs(MyCustomControl.ClickEvent); 
         RaiseEvent(args); 
      }
		
   } 
}

下面是 C# 中的自定义路由事件实现,当用户单击它时,它将显示一个消息框。

using System.Windows;  

namespace WPFCustomRoutedEvent { 
   // <summary> 
      // Interaction logic for MainWindow.xaml
   // </summary> 
	
   public partial class MainWindow : Window { 
	
      public MainWindow() { 
         InitializeComponent(); 
      }  
		
      private void MyCustomControl_Click(object sender, RoutedEventArgs e) { 
         MessageBox.Show("It is the custom routed event of your custom control"); 
      } 
		
   } 
}

以下是 MainWindow.xaml 中的实现,用于添加带有路由事件 Click 的自定义控件。

<Window x:Class = "WPFCustomRoutedEvent.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:local = "clr-namespace:WPFCustomRoutedEvent"
   Title = "MainWindow" Height = "350" Width = "604"> 
	
   <Grid> 
      <local:MyCustomControl Click = "MyCustomControl_Click" /> 
   </Grid> 
	
</Window>

当上面的代码被编译并执行时,它将产生以下包含自定义控件的窗口。

自定义控件

当您单击自定义控件时,它将产生以下消息。

单击自定义控件.jpg