CH05_路由事件

第5章:路由事件

本章目标

  • 理解路由事件

  • 掌握键盘输入事件

  • 掌握鼠标输入事件

  • 掌握多点触控输入事件

理解路由事件

​ 每个.NET 开发人员都熟悉“事件”的思想-------当有意义的事情发生时,由对象(如WPF元素)发送的用于通知代码的消息。WPF 通过事件路由(event routing)的概念增强了.NET 事件模型。事件路由允许源自某个元素的事件由另一个元素引发。例如,使用事件路由,来自工具栏技钮的单击事件可在被代码处理之前上传到工具栏,然后上传到包含工具栏的窗口。

​ 事件路由为在最合适的位置编写紧凑的、组织良好的用于处理事件的代码提供了灵活性。
要使用 WPF 内容模型,事件路由也是必需的,内容模型允许使用许多不同的元素构建简单元素(如按钮),并且这些元素都拥有自己独立的事件集合。

定义、注册和封装路由事件

​ WPF 事件模型和WPF属性模型非常类似。与依赖项属性一样,路由事件由只读的静态字段表示,在静态构造函数中注册,并通过标准的.NET 事件定义进行封装。

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 对象,用于指示当事件发生时按下了哪些鼠标键:

 private void img_MouseUp(object sender, MouseButtonEventArgs e)
 {

 }

​ 在WPF中,如果事件不需要传递任何额外细节,可使用 RoutedEventArgs 类,该类包含了有关如何传递事件的一些细节。如果事件确实需要传递额外的信息,那么需要使用更特殊的继承自 RoutedEventArgs 的对象(如上面示例中的 MouseButtonEventArgs)。因为每个 WPF 事件参数类都继承在 RoutedEventArgs 类,所以每个 WPF 事件处理程序都可访问与事件路由相关的信息。

在这里插入图片描述

处理路由事件

​ 正如第2章所述,可使用多种方法关联事件处理程序。最常用的方法是为 XAML 标记添加事件特性。事件特性技照想要处理的事件命名,它的值就是事件处理程序方法的名称。下面的示例使用这一语法将Image 对象的Mouselp 事件连接到名为img_MouseUp 的事件处理程序:

<Image Source="happyface.jpg" Stretch="None" Name="img" MouseUp="img_MouseUp" />

​ 通常约定以 “元素名_事件名”的形式命名事件处理程序方法,但这不是必的。如果没有为元素定义名称(可能是因为不需要在代码的任何地方与元素进行交互),可考虑使用以下形式的事件名称:

<Button Click="btn_Click">Ok</Button>

​ 也可以使用代码连接事件。下面的代码和上面给出的XAML 标记具有相同的效果:

img.MouseUp +=new MouseButtonEventHandler(img_MouseUp);

​ 上面的代码创建了一个针对该事件具有正确签名的委托对象(在该例中,是 MouseButtonEventHandler 委托的实例),并将该委托指向img_MouseUp() 方法。然后将该委托添加到img.MouseUp 事件的已注册的事件处理程序列表中。C#还允许使用更精简的语法,隐式的创建合适的委托对象:

img.MouseUp +=img_MouseUp;

事件路由

​ 在WPF中,许多控件都是内容控件,而内容控件可包含任何类型以及大量的嵌套内容。例如,可以构建包含图形的按钮,创建混合了文本和图片内容的标签,或者为了实现滚动或折叠的显示效果而在特定容器中放置内容。甚至可以多次重复嵌套,直至达到所希望的层次深度。

​ 这种可以任意嵌套的能力也带来了一个有趣问题。例如,假设有如下标签,其中包含一个StackPanel 面板,该面板又包含了两块文本和一副图像:

<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 新件:

  1. Image.MouseDown 事件
  2. StackPanel.MouseDown 事件
  3. Label.MouseDown 事件

RoutedEventArgs 类

​ 在处理冒泡路由事件时,sender 参数提供了对整个链条上最后那个链接的引用。例如,在上面的示例中,如果事件在处理之前,从图像向上冒泡到标签,sender 参数就会引用标签对象。

​ 有些情况下,可能希望确定事件最初发生的位置。可从 RouteeEventArgs 类的属性(如下表所示)获得这一信息以及其他细节。由于所有 WPF 事件参数类继承自 RoutedEventArgs ,因此任何事件处理程序都可以使用这些属性。

名称说明
Source指示引发了事件的对象。
OriginalSource指示最初是什么对象引发了事件。通常与Source 属性值相同,但在某些特定情况下,该属性指向对象树中更深的层次。
RoutedEvent通过事件处理程序为触发的事件提供 RoutedEvent 对象(如静态的 UIElement.MouseUpEvent 对象)。
Handled该属性允许终止事件的冒泡或隧道过程。如果该控件将handled 属性设为true,那么事件就不会继续传递,也不会再为其他任何元素引发该事件。

冒泡路由事件

​ 以下案例演示了事件冒泡的过程,当单击标签中的一部分时,在列表中显示事件发生的顺序。

在这里插入图片描述

在这里插入图片描述

下面是所需的XAML标记:

<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#代码:

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();  
        }

        /// <summary>
        /// 事件数量
        /// </summary>
        int eventCounter = 0;

        /// <summary>
        /// 事件处理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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;
        }

        /// <summary>
        /// 单击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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 标志,也将接收到事件:

 public Window1()
 {
     InitializeComponent();

     //添加事件处理程序
     this.btnClear.AddHandler(UIElement.MouseUpEvent,new MouseButtonEventHandler(btnClear_MouseUp),true);
 }

 /// <summary>
 /// 事件处理程序
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
private void btnClear_MouseUp(object sender, MouseButtonEventArgs e)
{
    this.lstMessages.Items.Add("执行了MouseUp!");
}

附加事件

​ 假设在 StackPanel 面板中封装了一堆按钮,并希望在一个事件处理程序。但Click 事件支持事件冒泡,从而提供了一种更好的选择。可通过处理更高层次元素的 Click 事件(如包含按钮的StackPanel 面板)来处理所有按钮的Click 事件。看似前线的代码却不能工作

 <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 解析器会将其解释成错误。解决方案是以 “类名.事件名” 的形式使用不同的关联事件语法,下面是更正后的示例。

 <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#代码:

 /// <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标记代码:

<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后台代码:

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();
        }

        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:

<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:

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:

<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:

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 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 属性为键盘上的所有键提供了相同的信息。

页面:

<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>

后台:

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() 方法,该方法返回相对于所选元素的鼠标坐标。

在这里插入图片描述

页面:

<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>

后台:

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事件来处理数据。

页面:

<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>

后台:

/// <summary>
/// 鼠标按下事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void lblSource_MouseDown(object sender, MouseButtonEventArgs e)
{
    //触发源
    Label lbl = sender as Label;

    //设置拖放操作
    DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy);
}

/// <summary>
/// 拖放进入事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void lblTarget_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.Text))
        e.Effects= DragDropEffects.Copy;
    else
        e.Effects= DragDropEffects.None;
}

/// <summary>
/// 拖放的释放事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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 事件:

页面:

<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>

后台:

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
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        

        public MainWindow()
        {
            InitializeComponent();            
        }

        /// <summary>
        /// 元素集合
        /// </summary>
        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 面板上显示三幅图像。此后用户可使用移动、旋转以及缩放手势来移动、转动、缩小或放大图像。

在这里插入图片描述

页面:

<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>

后台:

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
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    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 对象确定当前速度—当操作结束时元素的移动速度—并设置希望的减速度。下面的示例为移动、缩放以及旋转手势添加了惯性:

 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元素中的实现方式。

课后作业

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

功夫熊猫大侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值