WPF的事件包括生命周期事件、输入事件、路由事件和行为等方面。
生命周期事件
当WPF程序运行时候,编译完成后会生成并调用一个程序入口函数,即Main
函数,Main
函数中会实例化App
对象,并调用App
对象的初始化函数和Run
函数。
关于Main
函数,可以在编译后查看程序集目录下obj\Debug文件夹中的App.g.i.cs文件。
一、应用事件
应用程序的生命周期事件可以在App.xaml.cs文件中的APP类的构造函数中进行定义。(没构造函数可以自己定义)
Startup
:在调用Application
对象的Run
方法时发生。
SessionEnding
:在用户通过注销或关闭操作系统结束Windows会话时发生。
Activated
:当应用程序中任意窗口成为前台应用程序时发生,也就是应用窗体获得焦点时。
Deactivated
:当应用程序中所有窗口停止作为前台应用程序时发生, 也就是应用程序完全失去焦点时。
Exit
:在应用程序关闭之前发生,无法取消。
public App()
{
Startup += MyStartUpFunc;
......
}
二、Browser类型的应用事件(Page开发)
Navigating
:在应用程序中的导航请求新导航时发生。
LoadCompleted
:在已经加载、分析并开始呈现应用程序中的导航器导航到的内容时发生。
Navigated
:在已经找到应用程序中的导航器要导航的内容时发生,尽管此时可内容可能尚未完成加载。
NavigatedFailed
:在应用程序中的导航器在导航到所请求内容时出现错误的情况下发生。
NavigationProgress
:在由应用程序中的导航器管理的下载过程中定期发生,以提供导航进度信息。
NavigationStopped
:在调用程序中的导航器的StopLoading
方法时发生,或者当导航器在当前导航正在进行期间请求了一个新导航时发生。
以上Browser
事件,如果是做窗口开发基本上都用不上,其中Navigating
事件会在应用启动时调用一次,来加载确认有没有Page
,需不需要进行导航。需要使用事件时同样可以在App
构造函数中订阅。
public App()
{
......
Navigating += NavigatingFunc;
......
}
三、异常捕获事件
DispatcherUnhandledException
:在应用程序UI线程引发异常但未进行处理时发生。
- 注意,这里说的是UI线程上的异常,此事件无法捕获UI线程外的异常。
AppDomain.CurrentDomain.UnhandledException
:应用程序上所有线程引发异常但未处理时发生。
- 这个事件可以捕获除
Task
线程外的所有线程发生的异常,比DispatcherUnhandledException
事件更加全面。
TaskScheduler.UnobservedTaskException
:Task
线程引发异常但没有进行处理时触发,专门捕获应用上的Task
异常。
- 需要注意的是这个事件的触发时机,此事件并不会在异常发生时就触发,要在#C进行垃圾回收后才会触发。触发时间有一定的不确定性。(可以过
GC.Collect()
主动进行垃圾回收)
public App()
{
......
DispatcherUnhandledException += MyExceptionFunc;
......
}
窗体常见事件
窗体的生命周期事件可以在对应窗体的cs文件的窗体类型构造函数中进行定义。
SourceInitialized
:操作系统给窗体分配句柄时触发。
- 说到这里,提及一下,与Winform不同,在WPF中,只有窗口具有句柄,窗口中的控件上是没有的。
ContentRendered
:对应窗体内容渲染时触发,一般在窗口首次呈现时触发。
Loaded
:窗体加载完成时触发。
Activated
:当前窗口成为前台应用程序时发生,也就是当前窗体获得焦点时触发。
Deactivated
:当前窗体失去焦点时触发。
Closing
:窗体关闭时触发。
Closed
:窗体关闭后触发。
输入事件
一、鼠标输入事件
WPF中的常用鼠标事件有:MouseEnter
、MouseLeave
、MouseDown
、MouseUp
、MouseMove
、MouseLeftButtonDown
、MouseLeftButtonUp
、MouseRightButtonDown
、MouseRightButtonUp
、MouseDoubleClick
这些都是一些很常用的事件,使用上都差不多,没啥好说的。
MouseLeftButtonDown诡异事件
有一点需要注意的是,在使用时会发现MouseLeftButtonDown
事件如果用在Button
控件上是不会触发(目前只发现MouseLeftButtonDown
事件这样而MouseLeftButtonDown
不会触发也导致了MouseLeftButtonUp
不会触发)的,原因有两点:
- 第一,WPF设计原则上的问题,控件在捕获了
MouseLeftButtonDown
事件后,会将该事件的Handled
设置为True
,这个属性是用在事件路由中的,当某个控件得到一个RoutedEvent
,就会检测Handled
是否为true
,为true
则忽略该事件。 - 控件本身的
Click
事件,相当于将MouseLeftButtonDown
事件抑制了,转换成了Click
事件。
对此有三种解决方案:
- 第一:使用
PreviewMouseLeftButtonDown
事件来代替。(最简单了) - 第二:在路由中去进行对应的处理。
- 第三:在初始化的函数里利用
UIElement
的AddHandler
方法,显式的增加这个事件。
二、键盘输入事件
KeyDown
、KeyUp
:键盘按下和松开事件。
- 可以通过事件函数的
KeyEventArgs
参数对象来获取实际按下的键盘信息。 System.Windows.Input
命名控件下有枚举类型Keys
,其中存放了与键盘上所有键对应的值。
private void Button_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
......
}
}
-
需要注意的是,WPF中,
Window
控件可以正常触发到PreviewKeyDown
或者KeyDown
事件,但是UserControl
无法直接捕获到这两个事件,原因是UserConrtol
默认是无法获取对焦的,因此如果希望在UserControl
中触发这两个事件,触发之前先要给UserControl
获取焦点。public partial class ComponentConfigDialog : UserControl { public ComponentConfigDialog() { InitializeComponent(); //为了实现键盘的KeyDown事件,UserControl无法自动获取焦点,所以在这里获取 Focusable = true; Focus(); } }
TextInput
:文本框输入事件,当文本输入时触发,但使用后发现跟MouseLeftButtonDown
类似,无法触发。可以使用PreviewTextInput
事件来代替。
除上述键盘输入事件外,常用的还有KeyPressEvent
事件,一次键盘完整的按下松开时触发,需要注意的是此事件函数接收的不是KeyEventArgs
对象而是KeyPressEventArgs
对象,两者在用法上略有不同。
此外,在WPF中,如果在C#编写过程中希望获取当前键盘上按着的修饰键是什么,可以使用Keyboard.Modifiers
。
-
Keyboard.Modifiers
:获取或设置当前正在按着的修饰键(例如Alt、Shift等),其有效值可以通过ModifierKeys
枚举获取。if (Keyboard.Modifiers == ModifierKeys.Alt) { ...... }
三、拖拽事件
拖拽接收事件
Drop
:在输入系统报告出现将此元素作为放置目标的基础放置事件时发生。其实也就是将其他控件拖拽到当前控件时,当前控件的Drop
事件就会触发。
- 前提条件是要将当前控件设置为允许接收拖放,设置
AllowDrop=True
。 - 作为收纳容器的控件,
Background
不能为null
,例如Canvas
默认就是null
的,这时要设置Background="Transparent"
,否则拖拽的控件会放置到上一层有Background
实例的控件上。
拖拽方法
DragDrop.DoDragDrop(DependencyObject dragSource, object data, DragDropEffects allowedEffects)
:启动控件的拖拽操作。
- dragSource:拖拽的控件对象。
- data:传递给收纳控件的
Drop
事件的处理函数的数据,为object
类型。注意,不能为null
,否则报错。 - allowedEffects:拖拽的数据模式,为枚举类型。感觉没啥用,就是鼠标的显示略有不同,一般使用
Copy
或者Move
就可以了。
拖拽接收事件的事件参数类型
DragEventArgs
:拖拽事件的参数类型。
-
Source
:接收拖放的容器控件对象。 -
Data
:获取IDataObject
数据对象。 -
GetPosition(IInputElement relativeTo)
:获取当前拖放位置与指定控件对象的相对坐标。- 也就是获取放开鼠标左键时,鼠标与
relativeTo
的相对位置,一般可以使用上述中的Source
对象。
- 也就是获取放开鼠标左键时,鼠标与
-
GetData(Type format)
:IDataObject
对象的实例方法(注意这里是IDataObject
的实例方法),获取传输数据中,指定数据类型的对象。 -
完整示例
xaml代码
<Grid ShowGridLines="True"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Border Height="80" Width="100" Background="Blue" MouseDown="Border_MouseDown"/> <Canvas Grid.Column="1" AllowDrop="True" Drop="Canvas_Drop" Background="Transparent"/> </Grid>
后台代码
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Border_MouseDown(object sender, MouseButtonEventArgs e) { DragDrop.DoDragDrop(sender as DependencyObject, sender, DragDropEffects.Move } private void Canvas_Drop(object sender, DragEventArgs e) { var receiver = e.Source as Canvas; var point = e.GetPosition(e.Source as IInputElement); var border = (Border)e.Data.GetData(typeof(Border)); var newBorder = new Border(); newBorder.Width = border.Width; newBorder.Height = border.Height; newBorder.Background = border.Background; receiver.Children.Add(newBorder); Canvas.SetTop(newBorder, point.Y - newBorder.Height/2); Canvas.SetLeft(newBorder, point.X - newBorder.Width/2); } }
这里可能会产生疑问,就是能不能直接将拖拽的控件作为数据传给收纳控件的Drop
事件函数,然后将其直接作为子类添加到收纳控件?看起来像是可以,然而实际上因为一个控件对象不能同时存在于两个控件容器中,所以不能这么处理,一般的做法是根据传递给收纳控件的数据,通过反射来创建一个新的控件实例然后添加到收纳控件中,或者根据现有的数据集合,给集合添加数据。
行为
行为可以看作是对一系列事件的封装,可以在行为类型中,对使用了行为的控件进行指定事件的订阅以及事件处理函数的定义。
行为并不是WPF中的核心部分,是Expression Blend的设计特性,可以用触发器来取代行为。当然了,行为使用起来还是比较方便的。
想要使用行为,首先要通过Nuget下载对应的库Microsoft.Xaml.Behaviors
。
一、Behavior
行为的使用其实就是对Behavior<T>
的使用,创建行为类型必须继承Behavior<T>
。
Behavior<T>
中有几个必须用到的成员,分别为OnAttached()
函数、OnDetaching()
函数和AssociatedObject
属性。
AssociatedObject
:Behavior
的属性成员,为当前使用行为的控件对象。
OnAttached()
:当WPF应用程序挂载使用了当前行为的控件对象时调用,一般用来给对应控件对象进行事件的订阅。
OnDetaching()
:当对应的控件对象销毁时调用,一般用来给控件对象取消事件的订阅,以减小对资源的占用。
public class BorderMoveBehavior : Behavior<Border>
{
protected override void OnAttached()
{
base.OnAttached();
//给控件对象进行多个事件的订阅,这里随便写一个意思一下
AssociatedObject.MouseDown += Method;
}
private void Method(object sender, MouseButtonEventArgs e)
{
//随便干点啥
}
protected override void OnDetaching()
{
base.OnDetaching();
//给订阅的事件全部取消订阅
AssociatedObject.MouseDown -= Method;
}
}
二、行为实例
这里以Border
控件的拖动行为为例进行学习。
创建行为类型
创建类型继承Behavior<T>
,然后重写OnAttached()
函数、OnDetaching()
函数。在函数中使用AssociatedObject
属性给控件对象进行事件订阅和取消订阅。
在本例中,逻辑编写过程中有一点需要注意的是鼠标的锁定问题:
-
当鼠标按下时必须将鼠标锁定到当前控件对象,否则当鼠标移动过快或者与碰撞物相接时,控件对象容易失去鼠标焦点。
-
当鼠标松开时,必须将鼠标解锁。
-
Mouse.Capture(IInputElement element[, CaptureMode captureMode])
:将鼠标对象锁定到指定的对象上,等价于obj.CaptureMouse()
。-
captureMode
:捕获模式,默认为CaptureMode.Element
。CaptureMode.Element 1 鼠标捕获应用于单个元素。 鼠标输入转至已捕获的元素。 CaptureMode.SubTree 2 鼠标捕获应用于元素的子树。 如果鼠标悬停在具有捕获的元素的子级上,则会将鼠标输入发送至该子元素。 否则,会将鼠标输入发送至具有鼠标捕获的元素。 CaptureMode.None 0 没有鼠标捕获。 鼠标输入转至鼠标下的元素。
-
-
Mouse.Capture(null)
:将鼠标对象从当前锁定对象上解除,等价于obj.ReleaseMouseCapture()
。 -
具体代码
public class BorderMoveBehavior : Behavior<Border> { protected override void OnAttached() { base.OnAttached(); //给控件对象进行多个事件的订阅,这里随便写一个意思一下 AssociatedObject.MouseLeftButtonDown += MouseDownMethod; AssociatedObject.MouseLeftButtonUp += MouseUpMethod; AssociatedObject.MouseMove += MouseMoveMethod; } //父类的Canvas容器对象 private Canvas parentCanvas = null; private bool isDragging = false; private Point mouseCurrentPoint; private void MouseMoveMethod(object sender, MouseEventArgs e) { if (isDragging) { // 相对于Canvas的坐标 Point point = e.GetPosition(parentCanvas); // 设置最新坐标 AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseCurrentPoint.Y); AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseCurrentPoint.X); } } private void MouseUpMethod(object sender, MouseButtonEventArgs e) { if (isDragging) { isDragging = false; //锁定鼠标到当前控件对象 //AssociatedObject.CaptureMouse(); Mouse.Capture(null); } } private void MouseDownMethod(object sender, MouseButtonEventArgs e) { isDragging = true; // Canvas if (parentCanvas == null) parentCanvas = (Canvas)VisualTreeHelper.GetParent(sender as Border); // 当前鼠标坐标 mouseCurrentPoint = e.GetPosition(sender as Border); // 鼠标锁定 //AssociatedObject.CaptureMouse(); Mouse.Capture(AssociatedObject); } protected override void OnDetaching() { base.OnDetaching(); //给订阅的事件全部取消订阅 AssociatedObject.MouseLeftButtonDown -= MouseDownMethod; AssociatedObject.MouseLeftButtonUp -= MouseUpMethod; AssociatedObject.MouseMove -= MouseMoveMethod; } }
在xaml中使用行为
<Window ......
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
......>
<Grid>
<Canvas>
<Border Background="Orange" Width="100" Height="100">
<i:Interaction.Behaviors>
<local:BorderMoveBehavior/>
</i:Interaction.Behaviors>
</Border>
<Border Background="Green" Width="100" Height="100" Canvas.Left="100" Canvas.Top="100">
<i:Interaction.Behaviors>
<local:BorderMoveBehavior/>
</i:Interaction.Behaviors>
</Border>
</Canvas>
</Grid>
</Window>
路由事件
一、逻辑树与可视树
逻辑树
简单的说,逻辑树就是我们在XAML中进行开发时,那些具有“实体”的控件元素所组成的逻辑层次。由开发过程中所关注的那些界面布局或控件元素组成。
<Window ......>
<Grid>
<DockPanel>
<Button/>
</DockPanel>
</Grid>
</Window>
如上述xaml代码所示,Window
、Grid
、DockPanel
、Button
这几个元素一起组成了逻辑树。
可视树
可视树是由界面上可见的元素构成的,这些元素主要是由从Visual
或者Visual3D
类中派生出来的类。例如上面的代码中、这些组成逻辑树的元素本身还包含了一些由Visual
或者Visual3D
类派生出的一些可视树的元素。这些组成逻辑树的元素以及其所包含的可视元素全部一起组成了界面的可视树。
遍历逻辑树与可视树
LogicalTreeHelper
:逻辑树的工具类,可以通过这个静态类对逻辑树进行操作。
VisualTreeHelper
:可视树的工具类,可以通过这个静态类对可视树进行操作。
- 遍历代码 (PS:这段代码来自网络文章,为了方便直接复制借鉴,后期再做改良)
-
创建类型定义遍历逻辑
public class WpfTreeHelper { static string GetTypeDescription(object obj) { return obj.GetType().FullName; } /// <summary> /// 获取逻辑树 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static TreeViewItem GetLogicTree(DependencyObject obj) { if (obj == null) { return null; } //创建逻辑树的节点 TreeViewItem treeItem = new TreeViewItem { Header = GetTypeDescription(obj), IsExpanded = true }; //循环遍历,获取逻辑树的所有子节点 foreach (var child in LogicalTreeHelper.GetChildren(obj)) { //递归调用 var item = GetLogicTree(child as DependencyObject); if (item != null) { treeItem.Items.Add(item); } } return treeItem; } /// <summary> /// 获取可视树 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static TreeViewItem GetVisualTree(DependencyObject obj) { if (obj == null) { return null; } TreeViewItem treeItem = new TreeViewItem() { Header = GetTypeDescription(obj), IsExpanded = true }; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { var child = VisualTreeHelper.GetChild(obj, i); var item = GetVisualTree(child); if (item != null) { treeItem.Items.Add(item); } } return treeItem; } }
-
xaml界面设计
<Window x:Class="WpfApp5.MainWindow" 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" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <DockPanel> <Button DockPanel.Dock="Top" Click="Button_Click" Content="获取逻辑树和可视树"></Button> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <DockPanel Grid.Column="0"> <TextBlock DockPanel.Dock="Top" Text="逻辑树"></TextBlock> <TreeView Name="tvLogicTree"></TreeView> </DockPanel> <DockPanel Grid.Column="1"> <TextBlock DockPanel.Dock="Top" Text="可视树"></TextBlock> <TreeView Name="tvVisualTree"></TreeView> </DockPanel> </Grid> </DockPanel> </Grid> </Window>
-
Button事件代码
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { tvLogicTree.Items.Add(WpfTreeHelper.GetLogicTree(this)); tvVisualTree.Items.Add(WpfTreeHelper.GetVisualTree(this)); } }
-
二、冒泡与隧道
WPF的事件都是由Window
对象接收并然后在视觉树上逐层传递到对应控件上的。
Windows系统的消息都是通过句柄来传递的,而WPF中,只有窗口对象拥有句柄,这也是为什么会从Window
对象开始隧道再冒泡返回,最后响应Windows系统。
在这里延申出了两个概念:冒泡与隧道。
<Window
......
ButtonBase.Click="Window_Click">
<Grid Background="Yellow" MouseLeftButtonDown="Grid_MouseLeftButtonDown" PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown">
<StackPanel Background="Red" Height="300" Width=" 300" ButtonBase.Click="StackPanel_Click">
<Border Margin="30" Background="Blue" Height="100" Width="100" MouseLeftButtonDown="Border_MouseLeftButtonDown"/>
<Button Margin="30" Background="LightBlue" Height="100" Width="100" Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
点击Button
点击Border
冒泡与隧道
查看上述代码,在WPF中,当点击Button
时,一个完整的事件传递过程应该是从1-7的。
其中1-4是从Window
接收到鼠标点击消息后逐层向下传递到目标控件对象Button
上,这个过程称为隧道。
当Button
触发事件后、会将事件消息原路向上传递,即4-7的过程,这个过程称之为冒泡。
事件优先级
从Debug输出台中打印出来的信息中可以知道,在事件消息传递的过程中,有两点需要注意:
- 第一点是,如果是同样的事件,默认情况下,引起消息传递的目标控件的事件优先级比其他控件的优先级要高,其他控件的事件只能等目标控件触发完事件之后才能触发,也就是要等到冒泡阶段沿路上的同样事件才会触发。
- 第二点是,
Preview
事件可以无视第一点,在隧道中只要遇到对应事件,就可以先触发,并且不影响其他控件的事件。
基本上,Preview
事件是在隧道中发生的,称之为预览事件(隧道事件);其他路由事件是在冒泡中触发的,称为冒泡事件。
注意,上面的示例中,如果Button没有设置Background
属性,那么在鼠标点击时除了自身控件会触发点击事件,父类容器也会触发对应事件。
三、控制事件的触发
1、阻止消息的传递
在开发过程中,如果希望事件消息在某个事件触发之后,路径上的对应事件不再触发,可以在节点事件函数中,将Handled
属性设置为true
。
WPF中,所有的路由事件都共享一个公共事件基类RoutedEventArgs
,该类中定义了有Handled
属性,Handle
属性用于告诉消息系统,这个事件是否已经被处理,如果是,则后续事件不需要再对该消息进行处理。
private void Button_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Button_Click");
e.Handled = true;
}
需要注意的是,仅仅是后续事件不再触发,消息依然在传递,这一点下面会讲到。
2、强制消息触发
如果再将Handled
属性设置为true
之后,又希望在后面的传播路径上的某个控件的对应事件在接收到消息后依然能够触发,可以通过显示添加事件的方式进行设置。
AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo)
:为调用对象添加路由事件,该方法为UIElement
定义的方法,而几乎所有的控件都继承了UIElement
,因此都可以通过该方法进行路由事件的添加。
- routedEvent:具体的事件类型。
- handler:事件的处理函数,通常会通过创建
RoutedEventHandler
对象来传入。 - handledEventsToo:是否不论事件是否已经处理完成,仍要处理该事件。(即无视
Handled
)
public MainWindow()
{
InitializeComponent();
Stack.AddHandler(Button.ClickEvent, new RoutedEventHandler(Force_StackPanel_Click), true);
}
可以看到,虽然在Button
的Click
事件中已经将Handled
设置为true
,StackPanel
的事件还是照样触发了。(这也说明了事件消息是正常传递的,只不过默认情况下遇到消息中的Handle
为true
时,事件就不触发了)
自定义路由事件
一、路由事件的定义
在WPF中,事件的定义需要两个要素:事件注册和事件包装。
1、事件的注册
事件注册通过EventManager
类的静态方法RegisterRoutedEvent
来实现。
RoutedEvent RegisterRoutedEvent(string name, RoutingStrategy routingStrategy, Type handlerType, Type ownerType)
:注册路由事件。
name
:事件名称。routingStrategy
:路由策略,为RoutingStrategy
枚举类型。RoutingStrategy.Tunnel
:隧道触发RoutingStrategy.Bubble
:冒泡触发RoutingStrategy.Direct
:路由事件不通过元素树路由,但支持其他路由的功能事件,如EventTriger
、EventSetter
。
handlerType
:事件类型,路由事件就是typeof(RoutedEventHandler)
,属性变化事件则可以使用typeof(RoutedPropertyChangedEventHandler<T>)
ownerType
:当前注册事件的拥有者的对应类型,例如typeof(MyEventControl)
。
public static readonly RoutedEvent ButtonClickedEvet =
EventManager.RegisterRoutedEvent("ButtonClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(EventTestControl));
2、事件的包装
事件的包装跟依赖属性的包装类似,创建事件属性(注意这里不是get
、set
,而是add
、remove
),不过使用的是AddHandler
和RemoveHandler
方法。
需要注意的是,与依赖属性一样,不要在add
与remove
块中添加除AddHandler
与RemoveHandler
以外的代码,这些代码并不会被执行。
public event RoutedEventHandler ButtonClicked
{
add { AddHandler(ButtonClickedEvet, value); }
remove { RemoveHandler(ButtonClickedEvet, value); }
}
二、事件的触发
自定义路由事件的触发,需要调用UIElement
中定义的RaiseEvent
方法。
RaiseEvent(RoutedEventArgs e)
:引发指定的路由事件。
e
:路由事件参数类型对象,该对象包含了与路由事件相关联的状态信息和事件数据。
RoutedEventArgs(RoutedEvent routedEvent)
:根据指定的路由事件,创建路由事件参数RoutedEventArgs
对象。
RoutedPropertyChangedEventArgs<T>(T oldValue, T newValue, RoutedEvent routedEvent)
:创建路由事件参数对象,可以携带两个值,配合依赖属性变化事件使用。
//用户控件的xaml中的某个按钮元素的事件绑定到此方法
private void Button_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs args = new RoutedEventArgs(ButtonClickedEvet);
RaiseEvent(args);
}
三、完整的示例
注意,这个示例是在Prism框架下使用的,在MainWindow程序集中添加了一个用户控件EventTestControl。
EventTestControl.xaml
<UserControl x:Class="WpfControlLibrary.EventTestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfControlLibrary"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button Click="Button_Click" Content="TestButton"/>
</Grid>
</UserControl>
EventTestControl.xaml.cs
public partial class EventTestControl : UserControl
{
public EventTestControl()
{
InitializeComponent();
}
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(EventTestControl), new PropertyMetadata(0,ValuePropertyChangedCallback));
private static void ValuePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is object && d is EventTestControl)
{
EventTestControl etc = d as EventTestControl;
etc.OnValueChenged((int)e.OldValue, (int)e.NewValue);
}
}
protected void OnValueChenged(int oldValue, int newValue)
{
RoutedPropertyChangedEventArgs<int> args = new RoutedPropertyChangedEventArgs<int>(oldValue, newValue, ValueChangedEvent);
RaiseEvent(args);
}
//定义路由事件-Value属性变化事件
public static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<int>), typeof(EventTestControl));
public event RoutedPropertyChangedEventHandler<int> ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
//定义路由事件-用户控件中的某个按钮点击时触发
public static readonly RoutedEvent ButtonClickedEvet =
EventManager.RegisterRoutedEvent("ButtonClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(EventTestControl));
public event RoutedEventHandler ButtonClicked
{
add { AddHandler(ButtonClickedEvet, value); }
remove { RemoveHandler(ButtonClickedEvet, value); }
}
//用户控件中的某个按钮的事件绑定到此方法
private void Button_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs args = new RoutedEventArgs(ButtonClickedEvet);
RaiseEvent(args);
}
}
MainWindowViewModel.cs
public class MainWindowViewModel:BindableBase
{
private int _value;
public int Value
{
get { return _value; }
set { SetProperty(ref _value, value);}
}
}
MainWindow.xaml
<Grid>
<Slider Maximum="100" Value="{Binding Value}"/>
<local:EventTestControl Width="100" Height="50"
ButtonClicked="EventTestControl_ButtonClicked"
ValueChanged="EventTestControl_ValueChanged"
Value="{Binding Value}"/>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void EventTestControl_ButtonClicked(object sender, RoutedEventArgs e)
{
Debug.Print("button clicked");
}
private void EventTestControl_ValueChanged(object sender, RoutedPropertyChangedEventArgs<int> e)
{
Debug.Print(e.NewValue.ToString());
}
}