摘要
WPF中有两种事件模型:一种是在WinForm时代就存在的CLR事件;另一种是WPF时代的路由事件,在WPF中常用的也就是路由事件了。
正文
我们先写个CLR事件
- 事件的拥有者:事件的拥有者就是事件的触发者,比如按钮被点击,那么按钮就是事件的拥有者;
- 事件的响应者:事件的响应者就是事件的处理者,比如我们在winform后置代码中声明的一个一个事件处理方法,拥有事件处理方法的from体就是事件的响应者;
- 事件订阅关系:要想一个事件被处理,需要让事件的响应者去订阅事件拥有者的事件,在winfrom中这一操作被具象化为在“小闪电”操作栏中对对应的事件关联上后置代码中的事件处理器。
public delegate void delCallback(int a);
public class CEvent
{
public event delCallback eventCallback;
public void Speed()
{
if(eventCallback != null)
{
eventCallback(new Random().Next(1,100));
}
}
}
<StackPanel>
<Label Name="lblInfo" Margin="10" Padding="10" FontSize="24" Foreground="Red"></Label>
<Button Name="btnRun" Content="Run" Margin="10" Padding="10" Click="btnRun_Click"></Button>
</StackPanel>
private void btnRun_Click(object sender, RoutedEventArgs e)
{
CEvent cEvent = new CEvent();
cEvent.eventCallback += CEvent_eventCallback;
cEvent.Speed();
}
private void CEvent_eventCallback(int a)
{
lblInfo.Content = a.ToString();
}
CLR事件特点
1、事件发起者与接收者是紧密关联,不容易解耦
2、如果利用事件传递消息,必须是逐级传递,比较繁琐,也不容易管理;
路由事件
提到路由事件,首先一点,什么是路由呢?这里引入《深入浅出WPF》一书中对路由的解释:“起点与终点间有若干个中转站,从起点出发后经过每个中转站时要做出选择,最终以正确(比如最短或者最快)的路径到达终点。” 路由描述的就是这样的一个过程。
路由事件,是指事件的拥有者和响应者不必建立订阅关系,拥有者只管激发事件,响应者通过在自身设置事件监听器去监听对应的事件,并可以决定事件是否继续传播,如果说原始事件是两个人窃窃私语的话,那路由事件就是一队人挨个传话。当事件响应者通过事件监听器监听到某个事件的发生,通过事件携带的参数可以获取到事件的来源,从而做出判断该事件是否是自己关心的某个控件激发的,如果是,可以处理并停止事件的传播,如果不是,则放行不予理睬。
功能定义:路由事件是一种可以针对元素树中的多个*听器侦**(而不是仅针对引发该事件的对象)调用处理程序的事件。
实现定义:路由事件是由类的实例支持的 CLR 事件,由 RoutedEvent Windows Presentation Foundation (WPF) 事件系统进行处理。
路由事件一般使用以下三种路由策略:(所谓的路由策略就是指:路由事件实现遍历元素的方式)
冒泡:由事件源向上传递一直到根元素
直接:只有事件源才有机会响应事件
隧道:从元素树的根部调用事件处理程序并依次向下深入直到事件源
冒泡
向上传递的冒泡路由事件(bubbling event)比如MouseDown事件,是冒泡路由事件,该事件先由被单击的元素引发,接下来被该元素的父元素引发,然后被父元素的父元素引发,以此类推。直到元素树的顶部。
<StackPanel Name="sp1" MouseUp="sp1_MouseUp">
<TextBlock Margin="10" Text="点击" MouseUp="TextBlock_MouseUp"></TextBlock>
<TextBox Margin="10" Padding="10" Name="txt" MinLines="5"></TextBox>
</StackPanel>
private void sp1_MouseUp(object sender, MouseButtonEventArgs e)
{
count++;
txt.AppendText("StackPanel" + count.ToString() + "\n");
}
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
count++;
txt.AppendText("Btn" + count.ToString()+"\n");
}
直接
和普通的.NET事件类似,直接路由事件(direct event) 他们源于一个元素,不传递给其他元素,比如MouseEnter事件,是直接路由事件。
<Button Margin="10" Padding="10" Content="直接" MouseEnter="Button_MouseEnter"></Button>
隧道
向下传递的隧道路由事件(tunneling event) 比如PreviewKeyDown事件,隧道路由事件在事件到达恰当的空间之前为预览事件和终止事件提供了机会,比如PreviewKeyDown事件可以截获是否按下了某个键,首先在窗口级别上,然后是更具体的容器,直到当按下时具有焦点的元素。隧道路由命名都是Preview开头的。
<StackPanel Name="sp1" PreviewMouseUp="sp1_PreviewMouseUp">
<Button Margin="10" Padding="10" Content="隧道" PreviewMouseUp="Button_PreviewMouseUp"></Button>
</StackPanel>
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
count++;
txt.AppendText("Btn PreviewMouseUp" + count.ToString() + "\n");
}
private void sp1_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
count++;
txt.AppendText("sp1 PreviewMouseUp" + count.ToString() + "\n");
}
