1.使用路由事件
路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。通俗地说,路由事件会在可视树(逻辑树是其子集)上,上下routed,如果哪个节点上订阅了事件,就会被触发。
路由事件的规则有三种:
(1)冒泡;由事件源向上沿视觉树传递一直到根元素。如 MouseDown
(2)直接;只有事件源才有机会响应事件,某个元素引发事件后,不传递到其他元素
(3)隧道;从元素树的根部调用事件处理程序并依次向下深入直到事件源。 一般情况下,WPF提供的输入事件都是以隧道/冒泡对实现的。隧道事件常常被称为Preview事件。如 PreviewMouseDown
可以通过 e.handled = true 来停止路由。
using System;using System.Windows;using System.Windows.Controls;using System.Windows.Input;using System.Windows.Media;using System.Windows.Documents;namespace LY.ExamineRoutedEvents{ public class ExamineRoutedEvents : Application { static readonly FontFamily fontfam = new FontFamily("宋体"); const string strformat = "{0,-30},{1,-15},{2,-15},{3,-15}"; StackPanel stackOutput; DateTime dtLast; [STAThread] public static void Main() { ExamineRoutedEvents app = new ExamineRoutedEvents(); app.Run(); } protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Window win = new Window(); win.Title = "Examine Routed Events"; //建立Grid Grid grid = new Grid(); win.Content = grid; //建立三行 RowDefinition rowdef = new RowDefinition(); rowdef.Height = GridLength.Auto; grid.RowDefinitions.Add(rowdef); rowdef = new RowDefinition(); rowdef.Height = GridLength.Auto; grid.RowDefinitions.Add(rowdef); rowdef = new RowDefinition(); rowdef.Height = new GridLength(100, GridUnitType.Star); grid.RowDefinitions.Add(rowdef); //建立button,加入到grid Button btn = new Button(); btn.HorizontalAlignment = HorizontalAlignment.Center; btn.Padding = new Thickness(24); btn.Margin = new Thickness(24); grid.Children.Add(btn); //建立textblock,加入button TextBlock text = new TextBlock(); text.FontSize = 24; text.Text = win.Title; btn.Content = text; //建立标题,显示在ScrollViewer TextBlock textHeadings = new TextBlock(); textHeadings.FontFamily = fontfam; textHeadings.Inlines.Add(new Underline(new Run(string.Format(strformat, "Routed Events", "Sender", "Source", "OriginalSource")))); grid.Children.Add(textHeadings); Grid.SetRow(textHeadings, 1); //加入Scorllviewer ScrollViewer scroll = new ScrollViewer(); grid.Children.Add(scroll); Grid.SetRow(scroll, 2); //建立Stackpanel,放入Scorllviewer stackOutput = new StackPanel(); scroll.Content = stackOutput; //新增事件处理器 UIElement[] els = { win, grid, btn, text }; foreach (UIElement el in els) { // Keyboard el.PreviewKeyDown += AllPurposeEventHandler; el.PreviewKeyUp += AllPurposeEventHandler; el.PreviewTextInput += AllPurposeEventHandler; el.KeyDown += AllPurposeEventHandler; el.KeyUp += AllPurposeEventHandler; el.TextInput += AllPurposeEventHandler; // Mouse el.MouseDown += AllPurposeEventHandler; el.MouseUp += AllPurposeEventHandler; el.PreviewMouseDown += AllPurposeEventHandler; el.PreviewMouseUp += AllPurposeEventHandler; // Stylus el.StylusDown += AllPurposeEventHandler; el.StylusUp += AllPurposeEventHandler; el.PreviewStylusDown += AllPurposeEventHandler; el.PreviewStylusUp += AllPurposeEventHandler; //Window,Grid,Textblock类没有click事件,所以用在ButtonBase类中定义的AddHandler方法 el.AddHandler(Button.ClickEvent, new RoutedEventHandler(AllPurposeEventHandler)); } win.Show(); } void AllPurposeEventHandler(object sender, RoutedEventArgs e) { //如果有时间空隙,加入空格 DateTime dtNow = DateTime.Now; if (dtNow - dtLast > TimeSpan.FromMilliseconds(100)) stackOutput.Children.Add(new TextBlock(new Run())); dtLast = dtNow; //显示事件信息 TextBlock text = new TextBlock(); text.FontFamily = fontfam; text.Text = string.Format(strformat, e.RoutedEvent.Name, TypeWithoutNamespace(sender), TypeWithoutNamespace(e.Source), TypeWithoutNamespace(e.OriginalSource)); stackOutput.Children.Add(text); (stackOutput.Parent as ScrollViewer).ScrollToBottom(); } string TypeWithoutNamespace(object obj) { string[] str = obj.GetType().ToString().Split('.'); return str[str.Length-1]; } }}
2.自定义路由事件
(1)声明并注册路由事件
(2)为路由事件添加CLR事件包装
(3)创建可以激发路由事件的方法
以ButtonBase类中定义的Click路由事件为例:
public abstract class ButtonBase : ContentControl, ICommandSource{ //字段 声明路由事件 public static readonly RoutedEvent ClickEvent; //构造函数 在静态构造函数中注册路由事件,也可在直接申明字段同时注册路由事件 static ButtonBase() { //第1个参数为事件名 //第2个参数为路由策略,有三种策略:Bubble(冒泡式),Tunnel(隧道式),Direct(直达式) //第3个参数为用于指定事件处理器的类型,该类型必须为委托类型,并且不能为 null //第4个参数为路由事件所在的类名 ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase)); } //事件 将路由事件包装成CLR事件 public event RoutedEventHandler Click { add { base.AddHandler(ClickEvent, value); } remove { base.RemoveHandler(ClickEvent, value); } } //方法 创建激发事件的方法 protected virtual void OnClick() { RoutedEventArgs e = new RoutedEventArgs(ClickEvent, this); base.RaiseEvent(e); }}
进一步可参考:http://www.cnblogs.com/wilderhorse/articles/3231454.html
在进行上述自定义路由事件后,就可以像CLR事件一样,订阅事件和处理事件了。其中,订阅本身的事件同CLR事件一样,订阅子节点的事件用AddHandler方法,如上面“使用路由事件”中的例子。