备注:类关系结构图如下:
事件(Event)
Logical Tree 和 Visual Tree
- WPF 中有两种树,一种是逻辑树(Logical Tree),一种是可视元素树(Visual Tree)。我们所见到的所有树形结构都是 Logical Tree,Logical Tree最显著的特点就是它完全由布局组件和控件构成,每个 WPF 控件本身也是一棵由更细级别的组件组成,他们是 Visual Tree。大多数情况下我们都是和Logical Tree 打交道。
- 如果想在 Logical Tree 上查找元素,可以借助 LogicalTreeHelper 类的 static 方法。
- BringIntoViw:把选定的元素带进用户可视区域,经常用于可滚动的视图。
- FindLogicalNode:按照给定的名称(Name 属性)查找元素,包括子级树上的元素。
- GetChildren:查找所有直接子级元素。
- GetParent:获取直接父级元素。
- 如果想在 Visual Tree 上查找元素,则可以借助 VisualTreeHelper 类的 static 方法。
- 注意:当一个路由事件被激发之后是沿着 Visual Tree 走的,只有这样,藏在 Template 里的控件才能把消息送出来。
直接事件模型
- 事件的几个关键部分(以 Button 的点击事件为例,Button 的名字为 myButton):
- 事件的拥有者:myButton。
- 事件:myButton.Click
- 事件的响应者:窗体本身
- 事件处理器:this.myButton_Click 方法
- 订阅关系:
this.myButton.Click += new System.EventHandler(this.myButton_Click);
- myButton_click 方法代码
private void myButton_Click(object sender, EventArgs e) { if(sender is Button) { MessageBox.Show((sender as Button).Name); } }
- 直接事件模型的弊端
- 每对消息是“发送 -> 响应”关系,必须建立显示的点对点订阅关系。
- 事件的宿主必须能够直接访问事件的响应者,不然无法建立订阅关系。
路由事件模型
-
与 直接事件模型 区别:直接事件激发时,发送者直接将消息通过事件订阅关系传送给事件响应者,事件响应者进行响应。路由事件的拥有者和响应者没有直接显式的订阅关系,拥有者只负责激发事件,事件有谁响应他并不知道,他会沿着树进行传递,看谁进行侦听处理。
-
一个例子:
备注: -
AddHandler 方法源自 UIElement 类,也就是说,所有 UI 控件都具有这个方法。
-
Button.ClickEvent 就像每个依赖属性都拥有自己的 CLR 属性包装一样,每个路由事件都拥有自己的 CLR 事件。
-
private void ButtonClicked(object sender, RoutedEventArgs e)
中的 sender 实际上是 gridRoot 而不是被单击的 Button,这与传统的直接事件不太一样,如果想看事件的源头,可以(e.OriginalSource as FrameworkElement).Name;
-
上面为元素添加路由事件也可以在 XAML 中写:
<Grid x:Name="gridRoot" Background="Lime" Button.Click="ButtonClicked" ></Grid>
自定义路由事件
- 一个例子
- 备注:
- 创建自定义的路由事件大体可以分为下面三步:
- 声明并注册路由事件。
- 为路由事件添加 CLR 事件包装器。
- 创建可以激发路由事件的方法。
- 为路由事件添加 CLR 事件和依赖属性不一样,那个是 get 和 set,这个是 add 和 remove。
- 激发路由事件用的是 RaiseEvent 方法,传统直接事件激发是通过 Invoke 方法。
- 事件注册参数说明:
- 第一个参数和 CLR 事件包装器的名称一致。
- 第二个参数为路有时间的策略,有三种:**Bubble(冒泡式)**表示有里往外,**Tunnel(隧道式)**表示由外到里,和 Bubble 相反,**Direct(直达式)**模仿 CLR 直接事件,直接将事件传达事件处理器。
- 第三参数为事件处理器类型。
- 第四个有路由事件宿主类型。
- 例子中有多处对路由事件进行了侦听。
- 注意 ReportTimeEventArgs 继承对象,他的作用就是将参数进行包装,以防有多个参数不好传参。
- 如何让一个路由事件在某个节点不在继续传递呢,使用
e.Handled=true
.
- 创建自定义的路由事件大体可以分为下面三步: