深入浅出WPF-08.Event( 事件)02
路由事件
为了方便程序中对象之间的通信常常需要我们定义一些路由事件。使用路由事件比直接事件方便得多。
创建自定义路由事件的步骤:
1)声明并注册路由
2)为路由事件添加CLR事件包装
3)创建可以激发路由事件的方法
// 声明并注册路由事件
public static readonly RoutedEvent IOLSelectionChangedEvent = EventManager.RegisterRoutedEvent("IOLSelection", RoutingStrategy.Bubble, typeof(EventHandler<IOLSelectionEvnetArgs>), typeof(ReportPageBase));;
// 为路由事件添加CLR事件包装器
public event RoutedEventHandler IOLSelectionChanged
{
add { this.AddHandler(IOLSelectionChangedEvent, value); }
remove { this.RemoveHandler(IOLSelectionChangedEvent, value); }
}
// 激发路由事件的方法。
protected virtual void OnIOLSelectionChanged()
{
IOLSelectionEvnetArgs newEvent = new IOLSelectionEvnetArgs(IOLSelectionChangedEvent, this);
newEvent.Right = true;
this.RaiseEvent(newEvent);
}
public class IOLSelectionEvnetArgs : RoutedEventArgs
{
public IOLSelectionEvnetArgs(RoutedEvent routedEvent, object source)
: base(routedEvent, source)
{
}
public bool Right { set; get; }
}
public ReportView()
{
InitializeComponent();
this.DataContext = CurPatientStudyModel.CreateInstance();
this.AddHandler(ReportPageDataBase.IOLSelectionChangedEvent, new EventHandler<IOLSelectionEvnetArgs>(OnIOLSelectionChanged));
}
路由定义的手法和依赖项属性极其相似,使用EventManager的RegisterRoutedEvent方法进行注册。
为路由事件添加CLR事件包装是为了把路由事件暴露得像一个传统的直接事件,仍然可以使用操作符+=为事件增加处理器,使用-=操作符为事件移除处理器。
激发路由事件很简单,首先创建需要让事件携带的消息(RoutedEventArgs类的实例),并把它与路由事件关联,然后调用元素的RaiseEvent方法把事件送出去。这个激发与传统的事件激发不同,传统直接事件的激发是通过调用CLR事件的Invoke方法实现,而路由事件的激发与作为其包装器的CLR事件毫不相干。
创建路由事件方法RegisterRoutedEvent的四个参数:
1)string类型,路由事件的名称,建议和RoutedEvent变量的前缀和CLR事件包装器的名称一致。
2)路由事件的策略,Bubble(冒泡式):路由事件由事件的激发者出发向他的上级容器一层一层路由,直到最外层容器。Tunnel(隧道式):事件的路由方向正好和Bubble相反,是由UI树的树根向事件激发控件移动。Direct(直达式):模仿CLR直接事件,直接将事件消息送达到事件处理器。
3)指定事件处理器的类型。事件处理器的返回值类型和参数列表必须与此参数指定的委托保持一致。
4)指明路由事件的宿主类型。与依赖属性相似,这个类型和第一个参数共同参与底层算法产生这个路由事件的Hashcode,并注册到程序的路由事件列表中。
注意一下,RoutedEventArgs具有一个bool类型的属性Handled,一旦这个属性被设置为true,就表明路由事件已经被处理了,路由事件就不会继续传递下去。
RoutedEventArgs包含了路由事件的消息。RoutedEventArgs有两个属性Source和OriginalSource,这两个属性都表示路由事件传递的起点(路由事件的源头)。只不过Source表示的是LogicalTree上的消息源头,而OriginalSource表示的是VisualTree上的源头。一般我们只使用Source来使用叶子节点作为消息源头处理就满足条件了。
事件也是可以附加的,我们称为附加事件(Attached Event)。路由事件的宿主都是一些拥有可视化实体的界面元素,而附件事件则不具备显示在用户界面上的能力。也就是说,附加事件的宿主没有界面渲染功能。我们可以把路由事件定义在一个普通的类中,当这个类中的某个属性值发生变化时,激发一个路由事件,使用界面元素捕获这个事件。在界面元素中可以使用AddHandler和RemoveHandler来增加或者移除事件处理器。
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
// 声明并注册路由事件
public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student));
// 为界面元素添加路由事件侦听
public static void AddNameChangedHandler(DependencyObject dobj, RoutedEventHandler h)
{
UIElement e = dobj as UIElement;
if (e != null)
{
e.AddHandler(Student.NameChangedEvent, h);
}
}
// 移除监听
public static void RemoveNameChangedHandler(DependencyObject dobj, RoutedEventHandler h)
{
UIElement e = dobj as UIElement;
if (e != null)
{
e.RemoveHandler(Student.NameChangedEvent, h);
}
}
}
public MainWindow()
{
InitializeComponent();
Student.AddNameChangedHandler(this,new RoutedEventHandler(NameChangedHandler));
}
private void NameChangedHandler(object sender, RoutedEventArgs e)
{
Console.WriteLine("NameChanged");
e.Handled = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Student stu = new Student() { Id = 1, Name = "liu" };
stu.Name = "li Test";
RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent,stu);
this.RaiseEvent(arg);
}
如果一个非UIElement派生类注册了路由事件,这个类的实例既不能自己激发(Raise)此路由事件,也不能自己侦听此路由事件。只能把这个事件的激发附着在具有RaiseEvent方法的对象上,借助这个对象的RaiseEvent方法把事件发送出去,事件的侦听也只能交给别的对象去做。