一、概述
属性和事件是.NET抽象模型的核心部分,是每位.NET编程人员都十分熟悉的主题,但是WPF将属性和事件都进行了升级。WPF创建了一个新的依赖属性系统,重写了传统的.NET属性以提高性能并集成新功能(例如数据绑定和动画)。同时使用更高级的路由事件功能替换普通的.NET事件,使事件可在元素树中向上冒泡和向下隧道传播,并沿着传播路径被事件处理程序处理。
二、路由事件有三种形式
1、直接路由事件
直接路由事件与普通.NET事件类似,他们源于一个元素,不传递给其他元素。例如,MouseEnter事件(当鼠标指针移到元素上时发生)是直接路由事件。
2、冒泡路由事件
在包含层次中向上传递。该事件首先由被单击的元素引发,接下来被该元素的父元素引发,然后被父元素的父元素引发,依次类推,直到WPF到达元素树的顶部为止。例如,MouseDown事件就是冒泡路由事件。
3、隧道路由事件
在包含层次中向下传递。隧道路由事件在事件到达恰当的控件之前为预览事件(甚至终止事件)提供了机会。例如,通过PreviewKeyDown事件可截获是否按下某个键。首先在窗口级别上,然后是更具体的容器,直到到达当按下键时具有焦点的元素。
三、路由事件的便利性
WPF中的许多控件都是内容控件,而内容控件可包含任何类型以及大量的嵌套内容。例如,有一个文本标签,其中包含一个StackPanel面板,该面板又包含两个文本和一个图像,具体XAML代码如下:
<Window x:Class="WpfApplication5_1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication5_1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label BorderBrush="Black" BorderThickness="1">
<StackPanel>
<TextBlock Margin="3">Image and picture label</TextBlock>
<Image Source="happyface.jpg" Stretch="None"></Image>
<TextBlock Margin="3">Courtesy of the StackPanel</TextBlock>
</StackPanel>
</Label>
</StackPanel>
</Window>
效果图如下:
放在WPF窗口中的所有元素都在一定层次上继承自UIElement类,包括Label、StackPanel、TextBlock和Image等。UIElement定义了一些核心事件。例如,每个继承自UIElement的类都提供MouseDown事件和MouseUp事件。
元素和控件基类框图如下:
如果现在有这样一个需求,要求单击Lable标签来触发同一个事件处理程序,显然可以为Lable标签内的每个元素的MouseDown或MouseUp事件关联同一个事件处理程序,但这样会使标记变得杂乱无章且难以维护。
注意:此处为什么使用MouseDown或MouseUp事件而不使用Click事件,这是因为大多数WPF元素没有提供Click事件,而是提供了更直接的MouseDown或MouseUp事件。Click事件专用于继承自ButtonBase的控件。
MouseDown或MouseUp事件都是冒泡路由事件,此时可在标签级别或更高级别处理MouseDown或MouseUp事件,就可以实现无论单击标签内的哪个元素都可以触发同一个事件处理程序,而代码仅需要一行。具体代码如下:
XAML标记代码:
<Window x:Class="WpfApplication5_1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication5_1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label BorderBrush="Black" BorderThickness="1" MouseDown="lbl_Event">
<StackPanel>
<TextBlock Margin="3">Image and picture label</TextBlock>
<Image Source="happyface.jpg" Stretch="None"></Image>
<TextBlock Margin="3">Courtesy of the StackPanel</TextBlock>
</StackPanel>
</Label>
</StackPanel>
</Window>
VS后台代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication5_1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void lbl_Event(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("触发源:" + e.Source); //e.Source指示引发了事件的对象
}
}
}
四、路由事件的两个参数
所有WPF事件都为事件签名使用熟悉的.NET约定。每个事件处理程序都有两个参数,具体如下:
1、第一个参数:sender指事件触发时调用该事件的对象。例如,上例中指的是Label对象。
2、第二个参数:RoutedEventArgs或其派生类。
RoutedEventArgs类属性 | |
名称 | 说明 |
Source | 指示引发了事件的对象。例如,上例中点击的Image或TextBlock对象。 |
OriginalSource | 指示最初是什么引发了事件。OriginalSource属性值通常与Source属性值相同 |
RoutedEvent | 为事件处理程序提供RoutedEvent对象 如果用同一个事件处理程序处理不同的事件,这一信息非常有用 |
Handled | 该属性允许终止事件的冒泡或隧道过程。 如果控件将Handled属性设为true,那么事件就不会继续传递,也不会再为其他任何元素引发该事件 |
五、代码下载
WpfApplication5_1.rar提取码:xu8v