在传统的.net中已经有了事件机制了,为什么在WPF中要加入路由事件来取代事件呢,最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,从而有了两个概念,LogicalTree 和 VisualTree,那么它们分别是什么呢,举个例子:
这就是LogicalTree,一个Grid里面镶嵌了其他控件或布局组件,这相当于一棵树中的叶子。而VisualTree呢?它就是一个树中的叶子里面的结构,用放大镜看一下,其实叶子里面的结构也是一颗树结构,这就是VisualTree了,例如
好了,既然WPF中使用这样的一个设计理念,路由事件就是特别为WPF而生的,它的功能就是可以将一个事件从触发点沿着树向上或者向下传播。而需要对这个事件作出反应的地方添加一个监听器,就会有相应的反应了,当然,它的传递是可以用代码来停止的。
下面先说一下WPF内置的路由事件和原理,然后我们来创建一个属于自己的路由事件。
(一)WPF内置的路由事件
这里引用一下《深入浅出WPF》中的一个很简单明了的例子:
Xaml代码如下:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="190" Width="246">
<Grid x:Name="GridRoot" Background="Lime">
<Grid x:Name="GridA" Margin="10" Background="Blue">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10">
<Button x:Name="ButtonLeft" Width="65" Height="100" Margin="10" Content="Left"></Button>
</Canvas>
<Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10">
<Button x:Name="ButtonRight" Width="65" Height="100" Margin="10" Content="Right"></Button>
</Canvas>
</Grid>
</Grid>
</Window>
界面如下:。
当单击Left按钮的时候,Button.Click事件被触发,并且沿着ButtonLeft→CanvasLeft→GridA→GridRoot→Window这条路线向上传递,当单击Right按钮就会沿着ButtonRight→CanvasRight→GridA→GridRoot→Window这条路线向上传递,这里还没有添加监听器,所以是没有反应的。
如何加入监听器,我们可以再XAML中添加,如这样:
<Grid x:Name="GridA" Margin="10" Background="Blue" Button.Click="Button_Click">
我们在GridA中添加了Button.Click="Button_Click"这个事件处理器,就是监听器,并且事件处理交由Button_Click负责,那么后台的Button_Click函数体是怎样的呢,我们看一下
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("我到达了:" + (sender as FrameworkElement).Name+"原来是你激发了我:"+(e.Source as FrameworkElement).Name);
}
我们分析一下,那两个参数到底是什么呢?
参数一:sender,这是听者,就是监听的地方,如果点击了Left按钮,那么Left按钮就会大声说:“我被点击了”这个事件向上传递,知道到了设有监听Button.Click事件的地方,这个地方就是sender。
参数二:是RoutEventArgs类型的,这个参数携带了一些重要信息,例如事件是从哪里来的,上一个传到哪里等,都可以利用这个参数来查询。
好了,我们运行一下程序。
注意:路由事件向上传递,这是冒泡的传递方式,是系统默认的,我们还可以设置它为隧道模式或者直接模式,下面自定义路由事件会说到。
(二)自定义路由事件
其实自定义路由事件和自定义依赖项属性十分相似,下来就开始吧
创建自定义路由事件分为3个步骤:
1:声明并注册路由事件。
2:利用CLR事件包装路由事件。
3:创建可以激发路由事件的方法。
好,现在我创建一个能够报告自己在哪个位置里面的路由事件,我们一起去控件里面旅游一下。
先创建继承RoutedEventArgs类的派生类用来携带位置消息
public class ReportLocationEventArgs:RoutedEventArgs
{
public ReportLocationEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }
public string Location { set; get; }
}
其中的Location属性是用来存放位置的.
然后就到主体了
public class ReportLocation : Button//继承Button 制造相关事件
{
//声明并定义路由事件
public static readonly RoutedEvent ReportLocationEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportLocationEventArgs>), typeof(ReportLocation));
//CLR事件包装器
public event RoutedEventHandler ReportTime
{
add { this.AddHandler(ReportLocationEvent, value); }
remove { this.RemoveHandler(ReportLocationEvent, value); }
}
//重写单击事件,激发路由事件
protected override void OnClick()
{
base.OnClick();
ReportLocationEventArgs Myargs = new ReportLocationEventArgs(ReportLocationEvent, this);
Myargs.Location = this.Name;
this.RaiseEvent(Myargs);
}
}
先声明并定义注册路由事件,我们用EventManager.RegisterRoutedEvent方法来注册的参数有4个。
第一个参数是路由事件的名称Name。
第二个参数是路由事件的传递方式,有三种第一种是Bubble是冒泡模式,这种模式是从触发点向上传递,知道最外层。第二种是Direct就是传统的事件一样的。第三种是隧道模式,这和冒泡的相反,向下传递。
第三个参数是路由事件处理器类型。
第四个参数是拥有这个路由事件的类型。
然后用就是包装路由事件了,语法如上图。
最后激发设定路由事件,这是使用RaiseEvent()方法来触发的。!!!!!
在XAML中,设计如下:
<Window x:Class="WPF路由事件.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF路由事件"
Title="MainWindow" Name="Window" Height="359" Width="255" local:ReportLocation.ReportTime="ReportTimeHandler">
<Grid x:Name="GridRoot" Background="Lime" local:ReportLocation.ReportTime="ReportTimeHandler" >
<Grid x:Name="GridA" Margin="10" Background="Blue" local:ReportLocation.ReportTime="ReportTimeHandler">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10" local:ReportLocation.ReportTime="ReportTimeHandler">
<local:ReportLocation x:Name="ButtonLeft" Width="65" Height="100" Content="Left" local:ReportLocation.ReportTime="ReportTimeHandler"></local:ReportLocation>
</Canvas>
<Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10">
<local:ReportLocation x:Name="ButtonRight" Width="65" Height="100" Content="Right" local:ReportLocation.ReportTime="ReportTimeHandler"></local:ReportLocation>
</Canvas>
<ListBox Name="listbox" Grid.ColumnSpan="2" HorizontalAlignment="Left" Height="134" Margin="10,165,0,0" VerticalAlignment="Top" Width="207"></ListBox>
</Grid>
</Grid>
</Window>
界面如下:
那么最后看一下事件处理器是怎样的
WPF路由事件学习转(二)