来自《深入浅出WPF》(刘铁猛)读书笔记
路由(Route):起点与终点间有若干个中转站,从起点出发后经个每个中转站时要做出选择,最终以正确(比如最短或最快)的路径到达终点。
WPF中有两种“树”:逻辑树(Logical Tree)和可视元素树(Visual Tree)。
如果把LogicalTree延伸至Template组件级别,我们得到VisualTree。实际工作中多数情况下,我们是在与LogicalTree打交道。
如果现在LogicalTree上导航或查找元素,可以借助LogicalTreeHelper类的static方法来实现:
1)BringIntoView:把选定的元素带进用户可是区域,经常用于可滚动的视图;
2)FindLogicalNode:按给定的名称(Name属性值)查找元素,包括子级树上的元素;
3)GetChildren:获取所有子级元素;
4)GetParent:获取直接父级元素。
如果想在Visual Tree上导航或查找元素,则可借助VisualTreeHelper类的static方法实现。
WPF的UI可以表示为LogicalTree和VisualTree,当一个路由事件被击发后会沿着VisualTree传递的。
消息驱动机制在事件模型中被简化为3个关键点:
a)事件拥有着:消息的发送者;
b)事件的响应者:消息的接受与处理;
c)事件的订阅:事件被关注。
只要支持事件的委托与影响事件得方法在签名上保持一致(即参数列表和返回值一致),则一个事件可以由多个事件处理器来响应(多播事件),一个事件处理器也可以用来响应多个事件。
直接事件模型的弊端:
A)每队消息是“发送-》响应”关系,必须建立显式的点对点订阅关系;
B)事件的宿主必须能够直接访问事件的响应者,不然无法建立订阅关系;
C)程序运行期在容器中动态生成一组相同控件,每个控件的同一个事件都使用同一个事件处理器来响应。面对这种情况,我们在动态生成控件的同时就需要显式
路由事件机制:拥有者和事件的响应者之间没有显式的订阅关系,事件拥有着只负责激发事件,事件有谁响应它并不知道,事件的响应者则安装有事件侦听器,针对某类事件进行侦听,当有此类事件传递至,此时事件响应者就使用事件处理器来响应事件并决定事件是否可以继续传递。
尽管WPF推出了路由事件机制,但它任然支持传统的直接事件模型。
实例:为gridRoot安装针对Button.Click事件的侦听器:
this.gridRoot.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonClicked));
ButtonClicked方法代码如下:
private void ButtonClicked(object sender,RoutedEventArgs e)
{
MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
}
因为路由事件(的消息)是从内部一层一层传递出来最后到达最外层的gridRoot,并且由gridRoot元素将事件消息交给ButtonClicked方法来处理,所以传入ButtonClicked方法参数sender实际上是gridRoot而不是被单击的Button,这与传统的直接事件不太一样。
以上代码使用XAML表示:
<Grid x:Name="gridRoot" Background="Lime" Button.Click="ButtonClicked">
自定义路由事件
创建自定义路由事件大体可以分为三步:
1)声明并注册路由事件;
2)为路由事件添加CLR事件包装;
3)创建可以激活路由事件的方法。
//声明并注册路由事件
public static readonly RoutedEvent ClickEvent=/*注册路由事件*/;
//为路由事件添加CLR事件包装
public event RoutedEventHandler Click
{
add{this.AddHandler(ClickEvent,value);}
remove{this.RemoveHandler(ClickEvent,value);}
}
//激发路由事件的方法,此方法在用户单击鼠标时会被Windows系统调用
protected virtual void OnClick()
{
RoutedEventArgs newEvent=new RoutedEventArgs(ButtonBase.ClickEvent,this);
this.RaiseEvent(newEvent);
}
定义路由事件与定义依赖属性的手法极为相似--为你的类声明一个由public static readonly修饰的RoutedEvent类型字段,然后使用EventManager类的RegisterRoutedEvent方法进行注册。
使用CLR属性包装与依赖属性的代码格式亦十分相近,只是关键字get和set被替换为add和remove。
激发路由事件很简单,首先创建需要让事件携带的消息(RoutedEventArgs类的实例)并把它与路由事件关联,然后调用元素的RaiseEvent方法(继承自UIElement类)把事件发送出去。注意,这与激发传统直接事件的方法不同,传统直接事件的激发是通过调用CLR事件的Invoke方法实现的,而路由事件的激发与作为其包装器的CLR事件无关。
完整的注册代码:
public static readonly RoutedEvent ClickEvent=EventManager.RegisterRoutedEvent ("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
如何让一个路由事件在某个节点处不再继续传递?办法非常简单:路由事件携带的事件参数必须是RoutedeventArgs类或其派生类的实例,RoutedeventArgs类具有一个bool类型属性Handled,一旦这个属性被设置为true,就表示路由事件已经被处理了,那么路由事件也就不必再往下传递了。