简介:每个.NET开发人员都熟悉"事件"的思想,当有意义的事情发生时,由对象(WPF元素)发送的用于通知代码的消息。WPF通过路由(event routing)的概念增强了.Net事件模型,事件路由允许源自某个元素的事件由另一个元素引发。
1、定义、注册和封装路由事件。
步骤:
1.1)、由只读的、静态的方式声明。
1.2)、在静态的构造函数中通过EventManager.RegisterRoutedEvent()方法注册。
1.3)、通过.Net事件定义进行封装。
例子:
public class ButtonBase:ContentControl{ //声明只读的、静态的、RoutedEvent类型的字段。 public static readonly RoutedEvent ClickEvent; ////// 在静态的构造函数中注册路由事件。 /// static ButtonBase() { //路由事件是使用EventManager.RegisterRoutedEvent()方法注册的。 //参数:Click:事件的名称。 //参数:RoutingStrategy.Bubble:路由的类型(冒泡路由、隧道路由)。 //参数:typeof(RoutedEventHandler):定义事件处理程序语法的委托。 //参数:typeof(ButtonBase):拥有事件的类。 ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase)); } //通过普通的.Net事件进行封装,从而使所有的.Net语言都能访问他,事件封装器可使用AddHandle()和RemoveHandle()方法添加删除已注册的程序。 public event RoutedEventHandler Click { add { //为指定的路由事件添加路由事件处理程序,并将该处理程序添加到当前元素的处理程序集合中。 base.AddHandler(ClickEvent, value); } remove { //从此元素移除指定的路由事件处理程序。 base.RemoveHandler(ClickEvent, value); } }}
2、共享路由事件。
与依赖项属性一样,可在类之间共享路由事件的定义,例如,UIElement(该类是所有普通WPF元素的起点),MouseUp事件是由System.WIndows.Input.Mouse类定义的,UIElement类和ContentElement类只通过Routed-Event.AddOwner()方法重用MouseUp事件。
UIElemtn.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(typeof(UIElement));
3、处理路由事件。
3.1)、Xaml的方式添加处理程序。
Xaml代码:
Image_MouseEnter处理函数:
private void Image_MouseEnter(object sender, MouseEventArgs e){ MessageBox.Show("MouseEnter事件");}
3.2)、通过+=使用代码链接。
创建了一个针对该事件具有正确签名的委托对象(该类中是MouseEventHandler委托的实例),并将该委托指向Img_MouseEnter()方法。然后将该委托添加到img1.MouseEnter事件的已注册的事件的处理程序列表中。
private void MainWindow_Loaded(object sender, RoutedEventArgs e){ //第一种写法。 img1.MouseEnter += new MouseEventHandler(Img_MouseEnter); //第二种写法。 img1.MouseEnter += Img_MouseEnter;}private void Img_MouseEnter(object sender, MouseEventArgs e){ MessageBox.Show("进入了MouseEnter事件");}
3.3)、通过AddHandler()方法添加事件处理程序。
private void MainWindow_Loaded(object sender, RoutedEventArgs e){ //Image.MouseEnterEvent:处理路由事件的标志符。 //new MouseEventHandler(Img_MouseEnter):处理程序实现的引用。 img1.AddHandler(Image.MouseEnterEvent, new MouseEventHandler(Img_MouseEnter));}private void Img_MouseEnter(object sender, MouseEventArgs e){ MessageBox.Show("进入了MouseEnter事件");}
4、断开事件处理程序。
4.1)、通过-=断开事件处理程序。
//第一种写法。img1.MouseEnter -= new MouseEventHandler(Img_MouseEnter);//第二种写法。img1.MouseEnter -= Img_MouseEnter;
4.2)、通过RemoveHandler()方法断开事件处理程序。
img1.RemoveHandler(Image.MouseEnterEvent, new MouseEventHandler(Img_MouseEnter));
5、事件路由。
WPF中,事件路由以3种方式出现:直接路由事件(Direct event)、冒泡路由事件(bubbing event)、隧道路由事件(tunneling)。
在WPF中,如果事件不需要传递任何额外细节,可使用RoutedEventArgs类,该类包含了一些有关如何传递事件的一些细节,如果事件确实需要传递一些额外的信息,那么需要使用更特殊的继承自RoutedEventArgs的对象,其中每个WPF事件参数类都继承自RoutedEventArgs类。
RoutedEventArgs类常用属性 | |
Source | 指示引发事件的对象。 |
OriginalSource | 指示最初是什么对象引发了事件。 |
RoutedEvent | 通过事件处理程序为触发的事件提供RoutedEvent对象,如果同一个事件处理程序处理不同的事件,这一信息是非常有用的。 |
Handled | 该属性允许终止事件的冒泡或隧道过程,如果将该属性设置为true,就终止了传递。 |
5.1)、直接路由。
源于同一个元素,不传递给其他。如元素的MouseEnter事件。
5.2)、冒泡路由。
从下到上传递。一般情况下,冒泡路由以Mouse开头,如MouseDown、MouseUp。
Xaml代码:
后台代码:
protected int eventCount = 0;private void SomethingClick(object sender, MouseButtonEventArgs e){ eventCount++; string message = "#" + eventCount.ToString() + ":\r\n" + " Sender: " + sender.ToString() + "\r\n" + " Source: " + e.Source + "\r\n" + " Original Source" + e.OriginalSource; //将内容加入到listBox中。 listBox.Items.Add(message); //如果将Handled属性设置为true,则终止传递。 //e.Handled = true;}
效果:
当点击了图片,会从下到上地发生冒泡路由,直到冒泡到最外层的父级元素。
5.3)、隧道路由。
从上到下传递。 隧道路由比较容易识别,冒泡以Preview开头,如PreviewMouseDown、PreviewMouseUp。WPF通常成对地定义冒泡路由事件和隧道路由事件,这意味着如果发现冒泡的MouseUp事件,还可以找到PreviewUp隧道事件,隧道路由事件总是在冒泡路由事件之前被触发。如果隧道路由事件标记为已处理过,就不会发生冒泡路由事件,这是因为两个事件共享RoutedEventArgs类。
Xaml代码:
后台代码:
public partial class MainWindow : Window{ public MainWindow() { InitializeComponent(); } int count = 0; private void SuiDaoThings(object sender, MouseButtonEventArgs e) { count++; string message = "#" + count + ":\r\n" + " Sender: " + sender + "\r\n" + " Source " + e.Source + "\r\n" + " Original" + e.OriginalSource; listBox1.Items.Add(message); }}
效果:
5.4)、处理挂起的事件。
有一种方法可接收被标记为处理过的事件,不过不是直接通过XAML关联事件处理程序,而是必须使用AddHandle()方法,AddHandle()方法提供了一个重载版本,该版本可以接收一个Boolean值作为他的第三个参数,如果将该参数设置了True,即使设置了Handle标志,也将接收事件。
Xaml代码,不直接指定事件处理函数:
后台代码:
public partial class MainWindow : Window{ public MainWindow() { InitializeComponent(); //通过AddHandle()方法添加事件处理程序。 img1.AddHandler(UIElement.MouseDownEvent,new MouseButtonEventHandler(SomethingClick),true); stackPanel1.AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(SomethingClick), true); label1.AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(SomethingClick), true); grid1.AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(SomethingClick), true); window1.AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(SomethingClick), true); } protected int eventCount = 0; private void SomethingClick(object sender, MouseButtonEventArgs e) { eventCount++; string message = "#" + eventCount.ToString() + ":\r\n" + " Sender: " + sender.ToString() + "\r\n" + " Source: " + e.Source + "\r\n" + " Original Source" + e.OriginalSource; listBox.Items.Add(message); //如果使用AddHandle()方法添加事件处理程序,那么设置Handled属性不会阻止路由,仍会继续向上冒泡路由传递。 e.Handled = true; }}
效果图,如果使用AddHandle()方法的重载设置为true的那个,设置e.Handled属性为True,会继续传递。
5.5)、附加事件。
Button元素有Click事件,而StackPanel元素则没有Click事件,如果要为StackPanel元素添加Click事件,则报错。这个时候附加事件的优势就体现出来了。
在Xmal中添加附加事件:
通过AddHandle()方法添加附加事件:
Xaml代码:
后台代码:
public partial class MainWindow : Window{ public MainWindow() { InitializeComponent(); //通过AddHandler方法添加附加事件。 stackPanel1.AddHandler(Button.ClickEvent, new RoutedEventHandler(DoSomeThing)); } private void DoSomeThing(object sender, RoutedEventArgs e) { }}
6、WPF事件。
一般情况下,WPF最重要的事件包括这5类,分别是:生命周期事件、鼠标事件、键盘事件、手写笔事件和多点触控事件。
6.1)、生命周期事件。
生命周期事件是在元素被初始化,加载或卸载时发生这些事情。都是在FrameworkElement类中定义的。
所有元素的生命周期 | |
Initialized | 当元素被初始化,并已根据Xaml标记设置了元素的属性之后发生,这时元素已经初始化,但窗口的其他部分可能尚未初始化,但是,此时还没应用样式和数据绑定,这时,IsInitialized属性为true。 |
Loaded | 当整个窗口已经初始化并应用了样式和数据绑定时,该事件发生,就是在元素呈现之前的最后一站,这时IsLoaded属性为true。 |
UnLoaded | 当元素被释放时,该事件发生。原因是包含元素的窗口被关闭或者特定的元素被从窗口中删除。 |
Window类的常用生命周期事件 | |
名称 | 说明 |
ContentRendered | 在窗口首次呈现后立即发生,ContentRendered事件表明窗口已经完全可见,并且已经准备好接收输入 |
Activated | 当用户切换到该窗口时发生(从其他窗口切换到当前窗口),当窗口第一次加载也会引发该事件 |
Deactivated | 当用户从该窗口切换到其他窗口时发生。 |
Closing | 当关闭窗口时发生,不管是用户关闭窗口,还是通过代码WIndow.Close()。 |
Closed | 当窗口已经关闭后发生,但是让然可以访问元素对象,当然是在UnLoaded事件尚未发生之前 |
如果只对执行控件的第一次初始化感兴趣,完成这项任务的最好时机是触发Loaded事件。
6.2)、输入事件。
输入事件是当用户使用某些种类的外设硬件进行交互时发生的事件,如鼠标、键盘、手写笔或者多触控屏。输入事件可通过继承自InputEventArgs的自定义事件参数类型传递额外的信息。
6.2.1)、键盘输入。
当用户按下键盘上的一个键时,就会发生一系列事件。键盘处理永远不是那么的简单,一些控件可能会挂起这些事件中的某些事件,最明显的是TextBox控件,他挂起了TextInut事件,TextBox控键还挂起了KeyDown事件,通常可以使用隧道路由事件。
所有元素的按下键盘的顺序 | ||
名称 | 路由类型 | 说明 |
PreviewKeyDown | 隧道 | 当按下一个键时发生 |
KeyDown | 路由 | 当按下一个键时发生 |
PreviewTextInput | 隧道 | 当输入完成且元素正在接收文本输入时发生,对不会产生文本输入的按键(Ctrl键、shift键、方向键等),不会引发该事件 |
TextInput | 路由 | 当输入完成且元素正在接收文本输入时发生,对不会产生文本输入的按键,不会引发该事件 |
PreviewKeyUp | 隧道 | 当释放一个键时发生 |
KeyUp | 路由 | 当释放一个键时发生 |
Xaml中定义:
后台代码:
//PreviewKeDown事件处理程序。private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e){ string message = string.Format("Event--->{0},Key--->{1}", e.RoutedEvent, e.Key); listBox1.Items.Add(message);}//KeyDown事件处理程序。private void TextBox_KeyDown(object sender, KeyEventArgs e){ string message = string.Format("Event--->{0},Key--->{1}", e.RoutedEvent, e.Key); listBox1.Items.Add(message);}//PreviewTextInput事件处理程序。private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e){ string message = string.Format("Event--->{0},Key--->{1}", e.RoutedEvent, e.Text); listBox1.Items.Add(message);}//TextInput事件处理程序,不会执行此事件处理程序,因为TextBox元素挂起了TextInput事件。private void TextBox_TextInput(object sender, TextCompositionEventArgs e){ string message = string.Format("Event--->{0},Key--->{1}", e.RoutedEvent, e.Text); listBox1.Items.Add(message);}//PreviewKeyUp事件处理程序。private void TextBox_PreviewKeyUp(object sender, KeyEventArgs e){ string message = string.Format("Event--->{0},Key--->{1}", e.RoutedEvent, e.Key); listBox1.Items.Add(message);}//KeyUp事件处理程序。private void TextBox_KeyUp(object sender, KeyEventArgs e){ string message = string.Format("Event--->{0},Key--->{1}", e.RoutedEvent, e.Key); listBox1.Items.Add(message);}//TextChanged事件处理程序。private void TextBox_TextChanged(object sender, TextChangedEventArgs e){ string message = string.Format("Event--->{0}", e.RoutedEvent); listBox1.Items.Add(message);}
效果图,在文本框中输入了一个K键:
6.2.2)、 焦点。
在Windows的世界中,用户每次只能使用一个控件,当前接收用户按键的控件是具有焦点的控件。为了让控件能接受焦点,必须将Focusable设置为true,这是所有控件的默认值,如果为TextBox元素的Focusable元素设置为false,就不会获取焦点了,可以通过TabIndex属性设置按下tab键后的顺序。
6.2.3)、获取键盘状态。
当发生按键事件时,经常需要知道更多的信息,不仅需要知道按下的是哪个键,其他键是否按下了也同样重要,特别是Shift键、Ctrl键、Alt键。
KeyBoardDevice属性提供了KeyBoardDevice类的一个实例,它的属性包含了当前是哪个元素具有焦点以及按下了哪些修饰键,可用代码显示。
Xmal代码:
后台代码:
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e){ if ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) { MessageBox.Show("你点击了Control键"); } //还可以使用KeyBoard类,该类和KeyboardDevice类非常类似,只是Keyboard类由静态成员构成。 if (Keyboard.IsKeyDown(Key.LeftCtrl)) { MessageBox.Show("按下了Ctrl键盘"); }}
6.2.4)、鼠标输入。
鼠标事件执行几个关联的任务,当鼠标移到某个元素上时,可通过最基本的鼠标事件进行响应,这些都是MouseEnter和MouseLeave(直接事件)。
所有元素的鼠标单击事件(按顺序排序) | ||
名称 | 路由类型 | 说明 |
PreviewMouseLeftButtonDown PreviewMouseRightButtonDown | 隧道 | 按下鼠标左键时发生 |
MouseLeftButtonDown MouseRightButtonDown | 隧道 | 按下鼠标左键时发生 |
PreviewMouseLeftButtonUp PreviewMouseRightButtonUp | 隧道 | 按下鼠标右键时发生 |
MouseLeftButtonUp MouseRightButtonUp | 隧道 | 按下鼠标右键时发生 |
PreviewMouseWheel | 隧道 | 鼠标滚轮动作 |
MouseWheel | 冒泡 | 鼠标滚轮动作 |
6.2.5)、捕获鼠标。
当鼠标被一个元素捕获时,就不能与其他元素进行交互了(不能单击窗口中的其他元素),鼠标捕获通常用于短时间的操作。
Xaml代码:
后台代码:
private void Button_Click(object sender, RoutedEventArgs e){ Mouse.Capture(btn1);}
当点击button按钮后,整个窗口的其他元素和按钮就不能点击了,只有把光标移开该窗口,然后点击一下,这个窗口的元素才可以点击。
6.2.6)、鼠标拖放。
拖放操作的方法和事件都集中在System.Windows.DragDrop类中。
拖放操作有以下3个步骤:
1)、用户单击元素,并保持鼠标按键为按下状态,这时,某些信息被搁置起来,并且拖放操作开始。
2)、用户将鼠标移动到其他元素上,如果该元素可接受正在拖动的内容的类型,鼠标指针会变成拖放图标,否则鼠标指针会变成内部有一条线的圆形。
3)、当用户释放鼠标键时,元素接收信息并决定如何处理接收到的信息,在没有释放鼠标键时,可按下Esc键取消该操作。
Xaml代码:
Hello,World!!! 今天天气好晴朗!!!
后台代码:
////// 源元素的MouseDown事件。/// /// /// private void lbl1_MouseDown(object sender, MouseButtonEventArgs e){ //转换成Label对象。 Label lbl = (Label)sender; //DragDrop类用于拖动操作。 //DoDragDrop()方法用于启动拖放操作。 源对象,源对象的属性,拖放的效果(复制,移动...) //DoDragDrop()方法重载参数:源对象,源对象的属性,拖放的效果(复制,移动...) DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Scroll);}////// 目标元素的Drop事件。/// /// /// private void lbl2_Drop(object sender, DragEventArgs e){ //获取指定数据对象,格式由字符串指定。 ((Label)sender).Content = e.Data.GetData(DataFormats.Text);}
6.2.7)、多点触控输入(适用于带触摸屏的电脑)。
所有元素的原始触控事件 | ||
名称 | 路由类型 | 说明 |
PreviewTouchDown | 隧道 | 用户触摸元素时发生 |
TouchDown | 冒泡 | 用户触摸元素时发生 |
PreviewTouchMove | 隧道 | 用户移动到触摸屏上的手指时发生 |
TouchMove | 冒泡 | 用户移动到触摸屏上的手指时发生 |
PreviewTouchUp | 隧道 | 用户移开手指,结束触摸时发生 |
TouchUp | 冒泡 | 用户移开手指,结束触摸时发生 |
TouchEnter | 无 | 触点从元素外进入元素内时发生 |
TouchLeave | 无 | 触点离开元素时发生 |
End。