第5章:路由事件 本章目标
理解路由事件
掌握键盘输入事件
掌握鼠标输入事件
掌握多点触控输入事件
理解路由事件 每个.NET 开发人员都熟悉“事件”的思想——-当有意义的事情发生时,由对象(如WPF元素)发送的用于通知代码的消息。WPF 通过事件路由(event routing)的概念增强了.NET 事件模型。事件路由允许源自某个元素的事件由另一个元素引发。例如,使用事件路由,来自工具栏技钮的单击事件可在被代码处理之前上传到工具栏,然后上传到包含工具栏的窗口。
事件路由为在最合适的位置编写紧凑的、组织良好的用于处理事件的代码提供了灵活性。 要使用 WPF 内容模型,事件路由也是必需的,内容模型允许使用许多不同的元素构建简单元素(如按钮),并且这些元素都拥有自己独立的事件集合。
定义、注册和封装路由事件 WPF 事件模型和WPF属性模型非常类似。与依赖项属性一样,路由事件由只读的静态字段表示,在静态构造函数中注册,并通过标准的.NET 事件定义进行封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 using System.ComponentModel;using System.Security;using System.Windows.Input;using System.Windows.Media;using MS.Internal.Commands;using MS.Internal.KnownBoxes;using MS.Internal.PresentationFramework;namespace System.Windows.Controls.Primitives ;[DefaultEvent("Click" ) ] [Localizability(LocalizationCategory.Button) ] public abstract class ButtonBase : ContentControl , ICommandSource { public static readonly RoutedEvent ClickEvent; static ButtonBase () { Click = EventManager.RegisterRoutedEvent("Click" , RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (ButtonBase)); } [Category("Behavior" ) ] public event RoutedEventHandler Click { add { AddHandler(ClickEvent, value ); } remove { RemoveHandler(ClickEvent, value ); } } }
依赖项属性是使用 DependencyProperty Register()方法注册的,而路由事件是使用EventManager.RegisteiRoutedEvent()方法注册的。当注册事件时,需要指定事件的名称、路由类型(稍后介绍与路由类型相关的更多细节)、定义事件处理程序语法的委托(在该例中是RoutedEventHandler)以及拥有事件的类(在该例中是 ButtonBase 类)。
通常,路由事件通过普通的.NET 事件进行封装,从而使所有.NET 语言都能访问它们。事件封装器可使用 AddHandler()和 RemoveHandler()方法添加和删除已注册的调用程序,这两个方法都在 FrameworkElement 基类中定义,并被每个 WPF 元素继承。
共享路由事件 与依赖项属性一样,可在类之间共享路由事件的定义。例如,UIElement(该类是所有普通WPF元素的起点)和ContentElemeat(该类是所有内容元素的起点,内容元素是可以被放入流文档中的单独内容片段)这两个基类都使用了 Mouselp 事件。MouseUp 事件是由 SystemWindows Lnput.Mouse 类定义的。UIElement 类和 ContentElement类只通过 Routed- Event. AddOwner()方法重用MouseUp 事件:
引发路由事件 与所有事件类似,定义类需要在一些情况下引发事件。到底在哪里发生是实现细节。然而,重要的细节是事件不是通过传统的.NET 事件封装器引发的,而是使用 RaiseEvent() 方法引发事件,所有元素都从 UIElement 类继承了该方法。下面是来自 ButtonBase 类深层的代码:
所有WPF事件都为事件签名使用熟悉的.NET 约定。每个事件处理程序的第一个参数(sender)都提供引发该事件的对象的引用。第二参数是EventArgs 对象,该对象与其他所有可能很重要的附加细节绑定在一起。例如,MouseUP 事件提供了一个 MouseEventArgs 对象,用于指示当事件发生时按下了哪些鼠标键:
1 2 3 4 private void img_MouseUp (object sender, MouseButtonEventArgs e ){ }
在WPF中,如果事件不需要传递任何额外细节,可使用 RoutedEventArgs 类,该类包含了有关如何传递事件的一些细节。如果事件确实需要传递额外的信息,那么需要使用更特殊的继承自 RoutedEventArgs 的对象(如上面示例中的 MouseButtonEventArgs )。因为每个 WPF 事件参数类都继承在 RoutedEventArgs 类,所以每个 WPF 事件处理程序都可访问与事件路由相关的信息。
处理路由事件 正如第2章所述,可使用多种方法关联事件处理程序。最常用的方法是为 XAML 标记添加事件特性。事件特性技照想要处理的事件命名,它的值就是事件处理程序方法的名称。下面的示例使用这一语法将Image 对象的Mouselp 事件连接到名为img_MouseUp 的事件处理程序:
1 <Image Source="happyface.jpg" Stretch="None" Name="img" MouseUp="img_MouseUp" />
通常约定以 “元素名_事件名”的形式命名事件处理程序方法,但这不是必的。如果没有为元素定义名称(可能是因为不需要在代码的任何地方与元素进行交互),可考虑使用以下形式的事件名称:
1 <Button Click="btn_Click">Ok</Button>
也可以使用代码连接事件。下面的代码和上面给出的XAML 标记具有相同的效果:
1 img.MouseUp +=new MouseButtonEventHandler(img_MouseUp);
上面的代码创建了一个针对该事件具有正确签名的委托对象(在该例中,是 MouseButtonEventHandler 委托的实例),并将该委托指向img_MouseUp() 方法。然后将该委托添加到img.MouseUp 事件的已注册的事件处理程序列表中。C#还允许使用更精简的语法,隐式的创建合适的委托对象:
1 img.MouseUp +=img_MouseUp;
事件路由 在WPF中,许多控件都是内容控件,而内容控件可包含任何类型以及大量的嵌套内容。例如,可以构建包含图形的按钮,创建混合了文本和图片内容的标签,或者为了实现滚动或折叠的显示效果而在特定容器中放置内容。甚至可以多次重复嵌套,直至达到所希望的层次深度。
这种可以任意嵌套的能力也带来了一个有趣问题。例如,假设有如下标签,其中包含一个StackPanel 面板,该面板又包含了两块文本和一副图像:
1 2 3 4 5 6 7 8 9 10 11 <Label BorderBrush="Black" BorderThickness="1"> <StackPanel> <TextBlock Margin="3"> Image and text label </TextBlock> <Image Source="happyface.jpg" Stretch="None" /> <TextBlock Margin="3"> Courtesy of the StackPanel </TextBlock> </StackPanel> </Label>
放在 WPF 窗口中的所有要素都在一定层次上继承自 UIElement 类,包括 Label、 StackPanel、TextBlock 和Image。UIElement 定义了一些核心事件。例如,每个维承自 UIElenent 的类部提供MouseDown 事件和MouseUp事件。
但当单击上面这个特辣标签中的图像部分时,想一想会发生什么事情。很明显,引发Image.MouseDown事件和 Image.MouseUp 事件是合情合理的。但如果希望采用相同的方式来处理标签上的所有单击事件,该怎么办呢?此时,不管用户单击了图像、某块文本还是标然内的空白处,都应当使用相同的代码进行响应。
显然,可为每个元素的 MouseDown或MouseUp 事件关联同一个事件处理程序,但这样会使标记交得杂乱无章且难以维护。WPF使用路由事件模型提供了一个更好的解决方案。路由事件实际上以下列三种方式出现:
与普通NET 事件类似的直接路由事件(direct event)。它们源于一个元素,不传递给其他元素。例如,MouseEnter 事件(当鼠标指针移到元素上时发生)是直接路由事件。
在包含层次中向上传递的冒泡路由事件(bubbling event)。例如,MouseDown 事件就是冒泡路由事件。该事件首先由被单击的元素引发,接下来被该元素的父元素引发,然后被父元素的父元素引发,依此类推,直到WPF 到达元素树的顶部为止。
在包含层次中向下传递的隧道路由事件(tunneling event)。隧道路由事件在事件到达恰当的控件之前为预览事件(甚至终止事件)提供了机会。例如,通过 PreviewKeyDown 事件可截获是否按下了某个键。首先在窗口級别上,然后是更具体的容器,直至到达当按下键时具有焦点的元素。
当使用 EventManager.RegisterEvent()方法注册路由事件时,需要传递一个 RoutingStrategy校举值,该值用于指示希望应用于事件的事件行为。
MouseUp 事件和MouseDown事件都是冒泡路由事件,因此现在可以确定在上面特球的标签示例中会发生什么事情。当单击标签上的图像部分时,按以下顺序触发MouseDown 新件:
Image.MouseDown 事件
StackPanel.MouseDown 事件
Label.MouseDown 事件
RoutedEventArgs 类 在处理冒泡路由事件时,sender 参数提供了对整个链条上最后那个链接的引用。例如,在上面的示例中,如果事件在处理之前,从图像向上冒泡到标签,sender 参数就会引用标签对象。
有些情况下,可能希望确定事件最初发生的位置。可从 RouteeEventArgs 类的属性(如下表所示)获得这一信息以及其他细节。由于所有 WPF 事件参数类继承自 RoutedEventArgs ,因此任何事件处理程序都可以使用这些属性。
名称
说明
Source
指示引发了事件的对象。
OriginalSource
指示最初是什么对象引发了事件。通常与Source 属性值相同,但在某些特定情况下,该属性指向对象树中更深的层次。
RoutedEvent
通过事件处理程序为触发的事件提供 RoutedEvent 对象(如静态的 UIElement.MouseUpEvent 对象)。
Handled
该属性允许终止事件的冒泡或隧道过程。如果该控件将handled 属性设为true,那么事件就不会继续传递,也不会再为其他任何元素引发该事件。
冒泡路由事件 以下案例演示了事件冒泡的过程,当单击标签中的一部分时,在列表中显示事件发生的顺序。
下面是所需的XAML标记:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <Window x:Class="Wpf教学案例.Window1" 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:Wpf教学案例" mc:Ignorable="d" xmlns:sys="clr-namespa" Title="Window1" Height="450" Width="800" MouseUp="SomethingClicked"> <Grid Margin="3" MouseUp="SomethingClicked"> <!--行设置--> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <Label Margin="5" Grid.Row="0" HorizontalAlignment="Left" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" MouseUp="SomethingClicked"> <StackPanel MouseUp="SomethingClicked"> <TextBlock Margin="3" MouseUp="SomethingClicked"> Text Block -1 </TextBlock> <Image Source="happyface.jpg" Stretch="None" MouseUp="SomethingClicked" Width="600" Height="350"/> <TextBlock Margin="3" MouseUp="SomethingClicked"> Text Block -2 </TextBlock> </StackPanel> </Label> <ListBox Grid.Row="1" Margin="3" Name="lstMessages"></ListBox> <CheckBox Grid.Row="2" Margin="3" Name="chkHandle">是否终止事件冒泡</CheckBox> <Button Grid.Row="3" Margin="3" Padding="3" HorizontalAlignment="Right" Name="btnClear" Click="btnClear_Click" >Clear List</Button> </Grid> </Window>
下面是业务逻辑的C#代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Controls.Primitives;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;namespace Wpf 教学案例{ public partial class Window1 : Window { public Window1 () { InitializeComponent(); } int eventCounter = 0 ; private void SomethingClicked (object sender, RoutedEventArgs e ) { eventCounter++; string message = "#" + eventCounter.ToString() + ":\r\n" + "sender:" + sender.ToString() + ":\r\n" + "source:" + e.Source + ":\r\n" + "orignal source:" + e.OriginalSource; this .lstMessages.Items.Add(message); e.Handled = (bool )chkHandle.IsChecked; } private void btnClear_Click (object sender, RoutedEventArgs e ) { this .eventCounter = 0 ; this .lstMessages.Items.Clear(); } } }
提示 :
大多数WPF 元素没有提供Click 事件,而是提供了更直接的 MouseDown 和 MouseUp 事件. Click 事件专用于基于按钮的控件。
处理挂起的事件 有一种方法可接收被标记为处理过的事件,不是通过XAML 关联事件处理程序,而是必须使用前面介绍的AddHandler() 方法。AddHandler() 方法提供了一个重载版本,该版本可以接收一个 Boolean 值作为它的第三个参数。如果将该参数设置为true, 那么即使设置了Handled 标志,也将接收到事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Window1 () { InitializeComponent(); this .btnClear.AddHandler(UIElement.MouseUpEvent,new MouseButtonEventHandler(btnClear_MouseUp),true ); } private void btnClear_MouseUp (object sender, MouseButtonEventArgs e ){ this .lstMessages.Items.Add("执行了MouseUp!" ); }
附加事件 假设在 StackPanel 面板中封装了一堆按钮,并希望在一个事件处理程序。但Click 事件支持事件冒泡,从而提供了一种更好的选择。可通过处理更高层次元素的 Click 事件(如包含按钮的StackPanel 面板)来处理所有按钮的Click 事件。看似前线的代码却不能工作 :
1 2 3 4 5 <StackPanel Click="btn_Click" Margin="5"> <Button x:Name="btn1">btn1</Button> <Button x:Name="btn2">btn1</Button> <Button x:Name="btn3">btn1</Button> </StackPanel>
问题在于 StackPanel 面板没有 Click 事件,所以 XAML 解析器会将其解释成错误。解决方案是以 “类名.事件名” 的形式使用不同的关联事件语法,下面是更正后的示例。
1 2 3 4 5 6 7 <StackPanel Button.Click="btn_Click" Margin="5"> <Button x:Name="btn1">btn1</Button> <Button x:Name="btn2">btn1</Button> <Button x:Name="btn3">btn1</Button> <Label Name="lblMsg" /> </StackPanel>
以下是对应C#代码:
1 2 3 4 5 6 7 8 9 /// <summary> /// 按钮单击事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Click(object sender, RoutedEventArgs e) { this.lblMsg.Content =e.OriginalSource.ToString(); }
隧道路由事件 隧道路由事件的工作方式和冒泡路由事件相同,但方向相反。根据.NET约定,隧道路由事件总是以单词Priview开头(如PreviewKeyDown)。而且,WPF通常成对地定义冒泡路由事件和隧道路由事件。这意味着如果发现冒泡的MouseUp事件,就还可以找到PreviewMouseUp隧道事件。隧道路由事件总在冒泡路由事件之前被触发。
注意:
如果将隧道路由事件标记为已处理过,那就不会发生冒泡路由事件了。这是因为两个事件共享RuotedEventArgs类的同一个实例。
应用场景:
如果需要执行一些预处理(例如,根据键盘上特定的键执行动作或过滤掉特定的鼠标动作),隧道路由事件是非常有用的。
下面通过PreviewKeyDown事件,实例演示事件的隧道路由过程。当在文本框中按下一个键时,事件首先在窗口触发,然后在整个层次结构中向下传递。如果在传递的任意位置将PreviewKeyDown事件标记为已处理过,就不会发生冒泡的KeyDown事件。
XAML标记代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <Window x:Class="Wpf教学案例.Window1" 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:Wpf教学案例" mc:Ignorable="d" xmlns:sys="clr-namespa" Title="Window1" Height="450" Width="800" PreviewKeyDown="SomeKeyPressed"> <Grid Margin="3" > <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Label PreviewKeyDown="SomeKeyPressed" Margin="5" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" HorizontalContentAlignment="Stretch"> <StackPanel PreviewKeyDown="SomeKeyPressed"> <TextBlock PreviewKeyDown="SomeKeyPressed" Margin="3" HorizontalAlignment="Center"> Image and text label</TextBlock> <Image PreviewKeyDown="SomeKeyPressed" Source="happyface.png" Stretch="None"/> <DockPanel Margin="0,5,0,0" PreviewKeyDown="SomeKeyPressed"> <TextBlock Margin="3"> Type here:</TextBlock> <TextBox PreviewKeyDown="SomeKeyPressed" KeyDown="SomeKeyPressed"></TextBox> </DockPanel> </StackPanel> </Label> <ListBox x:Name="lstMessages" Margin="5" Grid.Row="1"></ListBox> <CheckBox x:Name="chkHandle" Margin="5" Grid.Row="2">Handle first event</CheckBox> <Button Click="cmdClear_Click" Grid.Row="3" HorizontalAlignment="Right" Margin="5" Padding="3">Clear List</Button> </Grid> </Window>
cs后台代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Controls.Primitives;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;namespace Wpf 教学案例{ public partial class Window1 : Window { public Window1 () { InitializeComponent(); } protected int eventCounter = 0 ; private void SomeKeyPressed (object sender, RoutedEventArgs e ) { eventCounter++; string message = "#" + eventCounter.ToString() + ":\r\n" + " Sender: " + sender.ToString() + "\r\n" + " Source: " + e.Source + "\r\n" + " Original Source: " + e.OriginalSource + "\r\n" + " Event: " + e.RoutedEvent; lstMessages.Items.Add(message); e.Handled = (bool )chkHandle.IsChecked; } private void cmdClear_Click (object sender, RoutedEventArgs e ) { eventCounter = 0 ; lstMessages.Items.Clear(); } } }
演示效果如下:
注意:
上图中的第6项对于TextBox而言在处理完PreviewKeyDown隧道路由事件后又引发KeyDown冒泡路由事件。
当勾选Handle firste vent复选框后,再在文本框内按下键时显示效果如下:
WPF事件 WPF的事件包括生命周期事件、输入事件、路由事件和行为等方面。
生命周期事件 在WPF中,窗体和控件具有生命周期事件,这些事件允许在不同的阶段执行代码。以下是一些常见的生命周期事件:
所有元素的生命周期事件:
事件
描述
Initialized
在初始化期间引发。
Loaded
在控件加载到窗体上时引发。
Unloaded
在控件从窗体中卸载时引发。
Windows 类的生命周期事件:
事件
描述
SourceInitialized
这个事件发生在WPF 窗体的资源初始化完毕,并且可以通过WindowInteropHelper获得该窗体的句柄用来与Win32交互。
ContentRendered
当Window的内容(例如页面)完成渲染时触发。
Activated
当Window获得焦点成为活动窗口时触发。
Deactivated
当Window失去焦点不再是活动窗口时触发。
Closing
在Window尝试关闭时触发,允许取消关闭或执行清理操作。
Closed
在Window完全关闭并且不再可见时触发。
xaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <Window x:Class="Wpf教学案例.Window1" 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:Wpf教学案例" mc:Ignorable="d" xmlns:sys="clr-namespa" Title="Window1" Height="450" Width="800" Loaded="Window_Loaded" Activated="Window_Activated" Deactivated="Window_Deactivated" Closing="Window_Closing" Unloaded="Window_Unloaded"> <Grid> </Grid> </Window>
cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace Wpf教学案例 { /// <summary> /// Window1.xaml 的交互逻辑 /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { // 在窗体加载时执行的代码 } private void Window_Activated(object sender, EventArgs e) { // 在窗体激活时执行的代码 } private void Window_Deactivated(object sender, EventArgs e) { // 在窗体非激活状态时执行的代码 } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { // 在窗体关闭时执行的代码 } private void Window_Unloaded(object sender, RoutedEventArgs e) { // 在窗体卸载时执行的代码 } } }
输入事件 输入事件是当用户使用某些种类的外设硬件进行交互时发生的事件,例如鼠标、键盘、手写笔或多点触控屏。输入事件可通过继承在 InputEventArgs 的自定义事件参数类型传递额外的信息。
键盘输入 当用户按下键盘上的一个键时,就会发生一些列事件。
名称
路由类型
说明
Preview kyDown
隧道
当按下一个键时发生
KeyDown
冒泡
当按下一个键时发生
PreviewTextInput
隧道
当按下一个键且产生文本时发生
TextInput
冒泡
当按下一个键且产生文本时发生
PreviewKeyUp
隧道
当释放一个按键时发生
KeyUp
冒泡
当释放一个按键时发生
处理按键事件 以下案例在一个文本框中监视所有可能得键盘事件,并在发生时给出报告。
xaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <Window x:Class="Wpf教学案例.Window1" 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:Wpf教学案例" mc:Ignorable="d" xmlns:sys="clr-namespa" Title="Window1" Height="450" Width="800"> <Grid Margin="3" > <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Label Grid.Row="0" Margin="5" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" HorizontalContentAlignment="Stretch"> <StackPanel> <DockPanel Margin="0,5,0,0"> <TextBlock Margin="3"> Type here:</TextBlock> <TextBox Name="txtContent" PreviewKeyDown="KeyEvent" KeyDown="KeyEvent" PreviewTextInput="TextInput" TextInput="TextInput" PreviewKeyUp="KeyEvent" KeyUp="KeyEvent"></TextBox> </DockPanel> </StackPanel> </Label> <ListBox Grid.Row="1" x:Name="lstMessages" Margin="5"></ListBox> <CheckBox Grid.Row="2" x:Name="chkIgnoreRepeat" Margin="5" >Ignoe Repeated keys</CheckBox> <Button Grid.Row="3" Click="Button_Click" HorizontalAlignment="Right" Margin="5" Padding="3">Clear List</Button> </Grid> </Window>
cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Controls.Primitives;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;namespace Wpf 教学案例{ public partial class Window1 : Window { public Window1 () { InitializeComponent(); } private void KeyEvent (object sender, KeyEventArgs e ) { if ((bool )chkIgnoreRepeat.IsChecked && e.IsRepeat) return ; string message = "Event:" + e.RoutedEvent + " " + " key:" + e.Key; this .lstMessages.Items.Add(message); } private new void TextInput (object sender, TextCompositionEventArgs e ) { string message = "Event:" + e.RoutedEvent + " " + " text:" + e.Text; this .lstMessages.Items.Add(message); } private void Button_Click (object sender, RoutedEventArgs e ) { this .txtContent.Text = null ; this .lstMessages.Items.Clear(); } } }
焦点 在Windows世界中,用户每次只能使用一个控件。当前接收用户按键的控件是具有焦点的控件。有时,有焦点的控件外观有些不同。例如,WPF按钮使用蓝色阴影显示它具有的焦点。为让控件能接受焦点,必须将Focusable 属性设置为 true,这是所有控件的默认值。
为将焦点从一个元素移到另一个元素,用户可单击鼠标或使用Tab 键和方向键。如果希望获得控制使用 Tab 键转移焦点顺序的功能,可按数字顺序设置每个控件的 Tabindex 属性。 Tabindex 属性为0的控件首先获得焦点,然后是次高的 TabIndex 值。如果多个元素具有相同的 TabIndex 值,WPF 就使用自动 Tab 顺序。
TabIndex 属性是在 Control 类中定义的,在该类中还定义了 IsTabStop 属性。可通过 IsTabStop 属性设置为false 来阻止控件被包含进 Tab 键焦点顺序。
获取键盘状态 当发生按键事件时,经常需要知道更多信息,而不仅要知道按下的是哪个键。而且确定其它键是否同时被按下了也非常重要。这意味着可能需要检查其他键的状态,特别是 Shift、Ctrl 和Alt 等修饰键。
对于键盘事件(PreviewKeyDown、KeyDown、PreviewKeyUp 和 KeyUp), 获取这些信息比较容易。首先, KeyEventArgs 对象包含 KeyStates 属性,该属性反映触发事件的键的属性。更有用的是, KeyboardDevice 属性为键盘上的所有键提供了相同的信息。
页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <Window x:Class="WpfApp1.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:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions > <RowDefinition Height="30"></RowDefinition> <RowDefinition Height="auto"></RowDefinition> </Grid.RowDefinitions> <TextBox Grid.Row="0" KeyDown="TextBox_KeyDown"/> <Label Grid.Row="1" Name="lblInfo" Content="?" /> </Grid> </Window>
后台:
1 2 3 4 5 6 7 private void TextBox_KeyDown (object sender, KeyEventArgs e ){ if ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) { this .lblInfo.Content = "你按下了Ctrl键" ; } }
KeyboardDevice 属性还提供了几个简便方法,这些方法在下表中列出。
名称
说明
IsKeyDown()
当事件发生时,通知是否按下了该键。
IsKeyUp()
当事件发生时,通知是否释放了该键。
IsKeyToggled()
当事件发生时,通知该键是否处于“打开”状态。该方法只对那些能够打开、关闭的键有意义,如 CapsLock 键、ScrollLock键以及NumLock键。
GetKeyStates()
返回一个或多个KeyStates 枚举值,指明该键当前是否被释放了、按下了或处于切换状态。
鼠标输入 鼠标事件执行几个关联的任务,当鼠标移到某个元素上时,可通过最基本的鼠标事件进行响应,这些事件是 MouseEnter(当鼠标指针移到元素上时引发该事件)和MoseLeave( 当鼠标指针离开元素时引发该事件)。这两个事件都是直接事件,这意味着它们不使用冒泡和隧遗过程,而是源自一个元素并且只被该元素引发。考虑到控件嵌入到 WPF窗口的方式,这是合理的。
例如,如果有一个包含按钮的 StackPanel 面板,并将鼠标指针移到按钮上。那么首先会为这个 StckPanel 面板引发 MouseEnter 事件(当鼠标指针进入StackPanel 面板的边界时),然后为被钮引发 MouseEnter 事件(当鼠标指针移到按钮上时)。将鼠标指针移开时,首先为按钮。然后为 StackPanel 面板引发 MouseLeave 事件。
还可响应 PreviewMouseMove 事件(隧道路由事件)和 MouseMove 事件(冒泡路由事件),只要移动鼠标机会引发这两个事件。所有这些事件都为代码提供了相同的信息:MouseEventArgs 对象。MouseEventArgs 对象包含当事件发生时标识鼠标键状态的属性,以及 GetPosition() 方法,该方法返回相对于所选元素的鼠标坐标。
页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <Window x:Class="WpfApp1.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:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions > <RowDefinition Height="350"></RowDefinition> <RowDefinition Height="auto"></RowDefinition> </Grid.RowDefinitions> <TextBox Background="AliceBlue" Grid.Row="0" MouseMove="TextBox_MouseMove"/> <Label Grid.Row="1" Name="lblInfo" Content="坐标:" /> </Grid> </Window>
后台:
1 2 3 4 5 6 7 8 9 private void TextBox_MouseMove (object sender, MouseEventArgs e ){ Point p = e.GetPosition(this ); lblInfo.Content = string .Format("坐标:X:{0},Y:{1}" ,p.X,p.Y); }
鼠标单击 鼠标单击事件的引发方式和按键事件的引发方式有类似之处。区别是对于鼠标左键和鼠标右键引发不同的事件。下表根据它们的发生顺序列出了这些事件。除这些事件外,还有两个响应鼠标滚轮动作的事件:PreviewMouseWheel 和 MouseWheel。
名称
路由类型
说明
PreviewMouseLeftButtonDown PreviewMouseRightButtonDown
隧道
当按下鼠标按键时发生
MouseLeftButtonDown MouseRightButtonDown
冒泡
当按下鼠标按键时发生
PreviewMousetLeftButtonUp PreviewMousetRightButtonUp
隧道
当释放鼠标时发生
MouseLeftButtonUp MouseRightButtonUp
冒泡
当释放鼠标时发生
某些元素添加了更高级的鼠标事件。例如,Control 类添加了 PreviewMouseDoubleClick 事件和 MouseDoubleClick 事件,这两个事件代替了 MouseLeftButtonUp 事件。与此类似,对于Button 类,通过鼠标或键盘可触发Click 事件。
捕获鼠标 通常,元素每次接收到鼠标键“按下”事件后,不久后就会接收到对应的鼠标键“释放”事件。但情况不见得总是如此。例如,如果单击一个元素,保持按下鼠标键,然后移动鼠标指针离开该元素,这时该元素就不会接收到鼠标键释放事件。
某些情况下,可能希望通知鼠标键释放事件,即使鼠标键释放事件是在鼠标已经离开了原来的元素之后发生的。为此,需要调用Mouse.Caphure()方法并传递恰当的元素以捕获鼠标。此后,就会接收到鼠标键按下事件和释放事件,直到再次调用Mouse.Caphure()方法并传递空引用为止。当鼠标被一个元素捕获后,其他元素就不会接收到鼠标事件。这意味着用户不能单击窗口中其他位置的按钮,不能单击文本框的内部。鼠标捕获有时用于可以被拖放并可以改变尺寸的元素。
有些情况下,可能由于其他原因(不是您的错)丢失鼠标捕获。例如,如果需要显示系统对话框,Windows 可能会释放鼠标捕获。如果当鼠标键释放事件发生后没有释放鼠标,并且用单击了另一个应用程序中的窗口,也可能丢失鼠标捕获。无论哪种情况,都可以通过处理元素的 LostMouseCapture 事件来响应鼠标捕获的丢失。
当鼠标被一个元素捕获时,就不能与其他元素进行交互(例如,不能单击窗口中的其他元素)。鼠标捕获通常用于短时间的操作,如拖放。
鼠标拖放 本质上,拖放操作通过以下三个步骤进行:
(1)用户单击元素(或选择元素中的一块特定区域),并保持鼠标按键为按下状态。
(2)用户将鼠标移到其他元素上。
(3)当用户释放鼠标键时。
如果希望两个未提供内置拖放功能的元素之间进行拖放,例如:可能希望从Label对象或TextBox对象拖动文本、并放到一个标签中。对于这种情况,需要处理拖放事件。
拖放操作有两个方面:源和目标。为了创建拖放源,需要在某个位置调用 DragDrop.DoDragDrop() 方法来初始化拖放操作。此时确定拖放操作的源,搁置希望移动的内容,并指明允许什么样的拖放效果(复制、移动等)。
接收数据的元素需要将它的 AllowDrop 属性设置为true.此外,它还需要通过处理 Drop事件来处理数据。
页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <Window x:Class="WpfApp1.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:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions > <RowDefinition ></RowDefinition> <RowDefinition ></RowDefinition> <RowDefinition ></RowDefinition> </Grid.RowDefinitions> <Label Grid.Row="0" Name="lblSource" Content="标签1" MouseDown="lblSource_MouseDown" /> <Label Grid.Row="1" Name="lblTarget" Content="标签2" AllowDrop="True" DragEnter="lblTarget_DragEnter" Drop="lblTarget_Drop"/> <TextBox Grid.Row="2" Name="lblInfo" Text="文本框" /> </Grid> </Window>
后台:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private void lblSource_MouseDown (object sender, MouseButtonEventArgs e ){ Label lbl = sender as Label; DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy); } private void lblTarget_DragEnter (object sender, DragEventArgs e ){ if (e.Data.GetDataPresent(DataFormats.Text)) e.Effects= DragDropEffects.Copy; else e.Effects= DragDropEffects.None; } private void lblTarget_Drop (object sender, DragEventArgs e ){ ((Label)sender).Content=e.Data.GetData(DataFormats.Text); }
注意:
如果希望在两个应用程序之间传递数据,那么务必检查 System.Windows.Clipboard 类,该类提供了静态方法,用于在 Windows 剪贴板中放置数据,并以各种不同的格式检索剪贴板中的数据。
多点触控输入 多点触控(multi-touch)是通过触摸屏幕与应用程序进行交互的一种方式。多点触控输入和更传统的基于笔(pen-based)的输入的区别是多点触控识别手势(gesture)用户可移动多根手指以执行常见操作的特方式。例如,在触摸屏上放置两根手指并同时移动它们,这通常意味着“放大”,而以一根手指为支点转动另一根手指意味着“旋转”。并且因为用户直接在应用程序窗口中进行这些手势,所以每个手势自然会被连接到某个特定的对象。例如,简单的具有多点触控功能的应用程序,可能会在虚拟桌面上显示多幅图片,并且允许用户拖动、缩放以及旋转每幅图片,进而创建新的排列方式。
多点触控的输入层次 正如您在上面了解到的,WPF允许使用键盘和鼠标的高层次输入(例如单击和文本改变)和低层次输入(鼠标事件以及按键事件)。这很重要,因为有些应用程序需要加以更精细的控制。多点触控输入同样应用了这种多层次的输入方式,并且对于多点触控支持,WPF 提供了三个独立的层次:
(1)原始触控(raw touch) :这是最低级的支持,可访问用户执行的每个触控。缺点是由您的应用程序负责将单独的触控消息组合到一起,并对它们进行解释。如果不准备识别标准触摸手势,反而希望创建以独特方式响应多点触控输入的应用程序,使用原始触控是合理的。一个例子是绘图程序,例如 Windows7画图程序,该程序允许用户同时使用多根手指在触摸屏上绘图。
(2) 操作(manipulation) :这是一个简便的抽象层,该层将原始的多点触控输入转换成更有意义的手势,与 WPF 控件将一系列 MouseDown 和 MouseUp 事件解释为更高级的MouseDoubleClick 事件很相似。WPF 支持的通用手势包括移动(pan)、缩放(zoom)、旋转(rotate)以及轻按(tap)。
(3)内置的元素支持(built-in element support) :有些元素已对多点触控事件提供了内置支持,从而不需要再编写代码。例如,可滚动的控件支持触控移动,如ListBox、ListView、DataGrid、TextBox 以及 ScrolIViewer。
原始触控 与基本的鼠标和键盘事件一样,触控事件被内置到低级的 UIElement 以及 ContentElement 类。
名称
路由类型
说明
PreviewTouchDown
隧道
当用户触摸元素时发生
TouchDown
冒泡
当用户触摸元素时发生
PreviewTouchMove
隧道
当用户移动放到触摸屏上的手指时发生
TouchMove
冒泡
当用户移动放到触摸屏上的手指时发生
PreviewTouchUp
隧道
当用户移开手指,结束触摸时发生
TouchUp
冒泡
当用户移开手指,结束触摸时发生
TouchEnter
无
当触点从元素外进入元素内时发生
TouchLeave
无
当触点离开元素时发生
所有这些事件都提供了一个 TouchEventArgs 对象,该对象提供了两个重要成员。第一个是 GetTouchPoint()方法,该方法返回触控事件发生位置的坐标。第二个是 TouchDevice 属性,该属性返回一个 TouchDevice 对象。当用户在不同的位置按下两根手指,WPF 将它们作为两个触控设备,并为每个触控设备指定唯一的ID,当用户移动这些手指,并且触控事件发生时,代码可以通过 TouchDevice.Id 属性区分两个触点。
下面的示例演示了一个简单的原始触控程序。当用户在Canvas 控件上触摸时,应用程序添加一个小的椭圆元素以显示触点。然后,当用户移动手指时,代码移动椭圆从而使其跟随手指移动。为了创建这个示例,需要处理 TouchDown、TouchUp() 以及 TouchMove 事件:
页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <Window x:Class="WpfApp1.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:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Canvas x:Name="canvas" Background="LightSkyBlue" TouchDown="canvas_TouchDown" TouchUp="canvas_TouchUp" TouchMove="canvas_TouchMove"> </Canvas> </Window>
后台:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Markup;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow () { InitializeComponent(); } private Dictionary<int , UIElement> movingEllipses= new Dictionary<int , UIElement>(); private void canvas_TouchDown (object sender, TouchEventArgs e ) { Ellipse ellipse = new Ellipse(); ellipse.Width = 30 ; ellipse.Height=30 ; ellipse.Stroke=Brushes.White; ellipse.Fill=Brushes.Green; TouchPoint tp = e.GetTouchPoint(canvas); Canvas.SetTop(ellipse, tp.Bounds.Top); Canvas.SetLeft(ellipse,tp.Bounds.Left); movingEllipses[e.TouchDevice.Id] = ellipse; canvas.Children.Add(ellipse); } private void canvas_TouchUp (object sender, TouchEventArgs e ) { UIElement element = movingEllipses[e.TouchDevice.Id]; TouchPoint tp=e.GetTouchPoint(canvas); Canvas.SetTop(element, tp.Bounds.Top); Canvas.SetLeft(element, tp.Bounds.Left); } private void canvas_TouchMove (object sender, TouchEventArgs e ) { UIElement element = movingEllipses[e.TouchDevice.Id]; canvas.Children.Remove(element); movingEllipses.Remove(e.TouchDevice.Id); } } }
操作 该例使用基本的安排在Canvas 面板上显示三幅图像。此后用户可使用移动、旋转以及缩放手势来移动、转动、缩小或放大图像。
页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <Window x:Class="WpfApp1.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:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Canvas x:Name="canvas" ManipulationStarting="canvas_ManipulationStarting" ManipulationDelta="canvas_ManipulationDelta" > <Image Canvas.Top="10" Canvas.Left="10" Width="200" IsManipulationEnabled="True" Source="a.jpg"> <Image.RenderTransform> <MatrixTransform></MatrixTransform> </Image.RenderTransform> </Image> <Image Canvas.Top="30" Canvas.Left="350" Width="200" IsManipulationEnabled="True" Source="a.jpg"> <Image.RenderTransform> <MatrixTransform></MatrixTransform> </Image.RenderTransform> </Image> <Image Canvas.Top="100" Canvas.Left="200" Width="200" IsManipulationEnabled="True" Source="a.jpg"> <Image.RenderTransform> <MatrixTransform></MatrixTransform> </Image.RenderTransform> </Image> </Canvas> </Window>
后台:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Markup;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow () { InitializeComponent(); } private void canvas_ManipulationStarting (object sender, ManipulationStartingEventArgs e ) { e.ManipulationContainer = canvas; e.Mode = ManipulationModes.All; } private void canvas_ManipulationDelta (object sender, ManipulationDeltaEventArgs e ) { Image img = e.Source as Image; Matrix matrix = ((MatrixTransform)img.RenderTransform).Matrix; ManipulationDelta md = e.DeltaManipulation; Point center = new Point(img.ActualWidth / 2 , img.ActualHeight / 2 ); center = matrix.Transform(center); matrix.ScaleAt(md.Scale.X, md.Scale.Y, center.X, center.Y); matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y); matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y); ((MatrixTransform)img.RenderTransform).Matrix= matrix; } } }
惯性 WPF还有一层构建在基本操作支持之上的特性,称为慣性(intertia)。本质上,通过惯性可以更選真、更流畅地操作元素。
现在,如果用户用移动手势拖动图中的一幅图像,当手指从触摸屏上拾起时图像会立即停止移动。但如果启用了惯性特征,那么图像会维续移动非常短的一段时间,正常地减速。该特性为操作提供了势头的效果和感觉。当将元素拖动进它们不能穿过的边界时,惯性还会使元素被弹回,从而使它们的行为像是真实的物理对象。
为给上一个示例添加惯性特性,只需处理 ManipulationlaertiaStarting 事件。与其他操作事件一样,该事件从一幅图像开始并冒泡至 Canvas 面板。当用户结束手势并拾起手指释放元素时,触发ManipulationinertiaStarting 事件。这时,可使用 ManipulationinertiaStartingEventsAgs 对象确定当前速度—当操作结束时元素的移动速度—并设置希望的减速度。下面的示例为移动、缩放以及旋转手势添加了惯性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void canvas_ManipulationInertiaStarting (object sender, ManipulationInertiaStartingEventArgs e ){ e.TranslationBehavior=new InertiaTranslationBehavior(); e.TranslationBehavior.InitialVelocity = e.InitialVelocities.LinearVelocity; e.TranslationBehavior.DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0 ); e.ExpansionBehavior = new InertiaExpansionBehavior(); e.ExpansionBehavior.InitialVelocity = e.InitialVelocities.ExpansionVelocity; e.ExpansionBehavior.DesiredDeceleration = 0.1 * 96 / 1000.0 * 1000.0 ; e.RotationBehavior = new InertiaRotationBehavior(); e.RotationBehavior.InitialVelocity = e.InitialVelocities.AngularVelocity; e.RotationBehavior.DesiredDeceleration = 720 / (1000.0 * 1000.0 ); }
本章小结 本章深入分析了路由事件。首先研究了路由事件,并看到了它们是如何使开发人员能够在不同层次上处理事件的—直接在源中处理事件或在包含元素中处理事件。接下来,本章介绍了为能够处理键盘、鼠标以及多点触控输入,这些路由策略在 WPF元素中的实现方式。
课后作业 无