自定义路由事件
- 定义:在自定义控件中JSCNumericBox定义一个路由事件
#region 路由事件
// 1.定义路由事件
public static readonly RoutedEvent MyCustomEvent = EventManager.RegisterRoutedEvent(
"MyCustom",//事件名称
RoutingStrategy.Bubble,//事件类型
typeof(RoutedEventHandler),// 事件处理程序类型
typeof(MainWindow)// 事件源类型
);
// 2.定义CLR事件包装器
public event RoutedEventHandler MyCustom
{
add { AddHandler(MyCustomEvent, value); }
remove { RemoveHandler(MyCustomEvent, value); }
}
// 3.触发路由事件的方法
protected void OnMyCustom()
{
// 创建事件参数(如果需要)
RoutedEventArgs args = new RoutedEventArgs(MyCustomEvent);
// 触发事件
RaiseEvent(args);
}
#endregion
- 使用:在窗体中使用自定义控件的路由事件
<local:JSCNumericBox Width="200" Height="50" MyCustom="JSCNumericBox_MyCustom"/>
private void JSCNumericBox_MyCustom(object sender, RoutedEventArgs e)
{
MessageBox.Show("路由事件触发");
e.Handled = true;// 阻止事件传递
}
这样你可以在JSCNumericBox里面任意地方编写逻辑调用OnMyCustom()来触发路由了。以下是完整示例:
public class JSCNumericBox : Control
{
#region 路由事件
// 1.定义路由事件
public static readonly RoutedEvent MyCustomEvent = EventManager.RegisterRoutedEvent(
"MyCustom",//事件名称
RoutingStrategy.Bubble,//事件类型
typeof(RoutedEventHandler),// 事件处理程序类型
typeof(MainWindow)// 事件源类型
);
// 2.定义CLR事件包装器
public event RoutedEventHandler MyCustom
{
add { AddHandler(MyCustomEvent, value); }
remove { RemoveHandler(MyCustomEvent, value); }
}
// 3.触发路由事件的方法
protected void OnMyCustom()
{
// 创建事件参数(如果需要)
RoutedEventArgs args = new RoutedEventArgs(MyCustomEvent);
// 触发事件
RaiseEvent(args);
}
#endregion
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
OnMyCustom();// 调用路由事件
}
static JSCNumericBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(JSCNumericBox), new FrameworkPropertyMetadata(typeof(JSCNumericBox)));
}
}
<Window x:Class="WPFLearn.Other.RoutedEvents.RoutedEventsWin"
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:WPFLearn.Other.RoutedEvents"
mc:Ignorable="d"
Title="RoutedEventsWin" Height="450" Width="800">
<Grid>
<local:JSCNumericBox Width="200" Height="50" MyCustom="JSCNumericBox_MyCustom"/>
</Grid>
</Window>
public partial class RoutedEventsWin : Window
{
public RoutedEventsWin()
{
InitializeComponent();
}
private void JSCNumericBox_MyCustom(object sender, RoutedEventArgs e)
{
MessageBox.Show("路由事件触发");
e.Handled = true;// 阻止事件传递
}
}
路由事件的类型
-
冒泡事件(Bubble):
事件从最具体的元素(触发事件的元素)开始,向上冒泡到其父元素,直到到达根元素。
例如,当用户点击一个按钮时,这个点击事件会首先在按钮上触发,然后逐级向上传递到按钮的父容器。
<Window x:Class="WPFLearn.Other.RoutedEvents.RoutedEventsWin"
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:WPFLearn.Other.RoutedEvents"
mc:Ignorable="d"
Title="RoutedEventsWin" Height="450" Width="800" Button.Click="btn1_Click">
<Grid>
<!--冒泡事件测试-->
<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>
</Grid>
</Window>
public partial class RoutedEventsWin : Window
{
public RoutedEventsWin()
{
InitializeComponent();
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource));
}
}

image.png
-
隧道事件(Tunnel):
事件从根元素开始,向下传递到最具体的元素。这种方式与冒泡相反。
Tunneling 事件的名称通常以 “Preview” 开头,例如 PreviewMouseDown,表示这是一个鼠标按下的隧道事件。
<Window x:Class="WPFLearn.Other.RoutedEvents.RoutedEventsWin"
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:WPFLearn.Other.RoutedEvents"
mc:Ignorable="d"
Title="RoutedEventsWin" Height="450" Width="800" 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>
public partial class RoutedEventsWin : Window
{
public RoutedEventsWin()
{
InitializeComponent();
}
private void btn1_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
this.txtInfo.AppendText(string.Format("当前响应事件对象:{0},响应事件原始对象:{1}\r\n", (sender as FrameworkElement).Name, e.OriginalSource));
}
}

image.png
-
直接事件(Direct):
只有源元素本身才有机会调用处理程序以进行响应,这些事件不经过路由过程,而是直接在触发它们的元素上发生,不会在元素树中传播。
// 示例:直接添加事件处理程序到直接事件
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button was clicked");
}
// 在构造函数中注册事件处理程序
public MainWindow()
{
InitializeComponent();
this.someButton.Click += new RoutedEventHandler(Button_Click);
}
-
阻止消息的传递
在开发过程中,如果希望事件消息在某个事件触发之后,路径上的对应事件不再触发,可以在节点事件函数中,将Handled属性设置为true。
WPF中,所有的路由事件都共享一个公共事件基类RoutedEventArgs,该类中定义了有Handled属性,Handle属性用于告诉消息系统,这个事件是否已经被处理,如果是,则后续事件不需要再对该消息进行处理。
<Window x:Class="WPFLearn.Other.RoutedEvents.RoutedEventsWin"
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:WPFLearn.Other.RoutedEvents"
mc:Ignorable="d"
Title="RoutedEventsWin" Height="450" Width="800">
<Grid>
<StackPanel HorizontalAlignment="Left" ButtonBase.Click="StackPanel_Click" VerticalAlignment="Center" Width="200" Height="300">
<Button Content="按钮1" Click="Button1_Click"/>
<Button Content="按钮2" Click="Button2_Click"/>
<Button Content="按钮3"/>
</StackPanel>
</Grid>
</Window>
public partial class RoutedEventsWin : Window
{
public RoutedEventsWin()
{
InitializeComponent();
}
#region 冒泡路由事件
private void StackPanel_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("StackPanel按钮点击!");
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("按钮1点击!");
e.Handled = true;//阻止继续冒泡
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("按钮2点击!");
}
#endregion
}
附加事件
路由事件的宿主是那些有 UI 显示功能的界面元素,而附加事件是那些没有 UI 显示功能的元素,其本质还是路由事件,只是路由事件的宿主不一样。附加事件只是路由事件的一种用法而已,比如依赖属性和附加属性。
常见的附加事件有:
- Binding类:SourceUpdated事件、TargetUpdated事件。
- Mouse类:MouseEnter事件、MouseLeave事件、MouseDown事件、MouseUp事件等。
- Keyboard类:KeyDown事件、KeyUp事件等。