多点触摸 - 管理(动手实验)

概述

Windows 7 使用户无需使用中间设备,通过手指触摸方式就能够管理应用程序。这扩展了平板电脑基于触笔的功能。与其他指点设备不同,这种新功能支持在不同指点位置上同时发生多个输入事件,支持复杂的场景,比如通过十指或由多个并行用户管理应用程序。然而,要成功实现此功能,我们必须调整应用程序的用户界面和行为,以支持这种新的输入模式。

本次动手实验 (Hands-On Lab, HOL) 的目标是将一个基于鼠标的简单图片操作应用程序升级为支持多点触摸的现代应用程序,类似于 Microsoft Surface 行为。

 

图 1

启用了多点触摸的应用程序

目标

本动手实验将学习如何管理多点触摸事件,包括:

•               理解同时操作多个对象的含义  

•               检查多点触摸硬件是否存在及其就绪情况

•               在 WPF 3.5SP1 中实现多点触摸事件

•               通过内置的 WPF 触笔事件使用多点触摸事件

•               使用操作 (Manipulation) 和惯性 (Inertia) 处理器

               

设置

为了方便起见,我们以 Visual Studio 代码片段的形式提供将在本动手实验中使用的许多代码。本实验所需的设置包括安装这些代码片段。为此:

1.            运行位于本实验的 Setup 文件夹下的 MultiTouchLab.vsi 安装程序。

2.            按照向导说明安装代码片段。

               

系统要求

要完成本实验,必须拥有以下工具:

•               Microsoft Visual Studio 2008 SP1

•               Windows 7

•               Windows 7 Integration Library 示例(Windows Touch:开发人员资源)

•               一台多点触摸硬件设备

               

练习 1 :开发多点触摸图片处理应用程序

要理解如何管理多点触摸输入,我们首先需要理解如何处理(基于鼠标的)单点输入。为此,我们准备了一个基于鼠标的图片处理应用程序,就是多点触摸动手实验初始应用程序。

任务 1 了解解决方案

1.            打开位于 %TrainingKitInstallDir%\MultiTouch\Ex1-PictureHandling\Begin 下的初始解决方案,选择想要使用的语言(C# 或 VB)。

2.            编译并运行它。可以进行的操作有:通过单击挑选一张图片;按住鼠标左键并移动鼠标来拖动图片;使用鼠标滚轮缩放图片。每次选择一张图片时,该图片就会出现在最前面。在开始编码之前,首先了解一下初始应用程序。

该应用程序用于处理图片。每张图片由一个 Picture 用户控件表示。这是一个非常简单的控件,它基于 WPF。Picture 用户控件的 XAML 如下:

XAML

<UserControl x:Class="MultitouchHOL.Picture"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">

    <Image Source="{Binding Path=ImagePath}" Stretch="Fill" Width="Auto"

           Height="Auto"  RenderTransformOrigin="0.5, 0.5">

        <Image.RenderTransform>

            <TransformGroup>

                <RotateTransform Angle="{Binding Path=Angle}"></RotateTransform>

                <ScaleTransform ScaleX="{Binding Path=ScaleX}"

                                    ScaleY="{Binding Path=ScaleY}">

                </ScaleTransform>

                <TranslateTransform X="{Binding Path=X}" Y="{Binding Path=Y}"/>

            </TransformGroup>

        </Image.RenderTransform>

    </Image>

</UserControl>

注意: 此用户控件的代码仅包括 ImagePath、Angle、ScaleX、ScaleY、X 和 Y 依赖属性的声明。ImagePath 是有效的图像文件或资源的路径。Angle 是图像的旋转角度。ScaleX 和 ScaleY 是图像的缩放系数,而 X、Y 是图像的中心位置。

3.            现在看一下 MainWindow 类。此 XAML 文件声明 MainWindow:

XAML

<Window x:Class="MultitouchHOL.MainWindow"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    Title="MultitouchHOL" Height="300" Width="300" WindowState="Maximized"

    xmlns:mt="clr-namespace:MultitouchHOL">

    <Canvas Name="_canvas">

    </Canvas>

</Window>

注意:此窗口仅包含一个画布元素 (_canvas)。画布是包含 Picture 用户控件实例的面板。

4.            现在打开 MainWindow.xaml.cs (C#) 或 MainWindow.xaml.vb 文件 (Visual Basic)。如果用户按住鼠标左键,_picture 成员将拥有当前跟踪的图片;否则,它将拥有空值。_prevLocation 是 Mouse Move 事件报告的上一个位置,用于计算偏移量。

5.            MainWindow 构造函数创建主窗口,注册各种事件处理函数。

C#

public MainWindow()

{

     InitializeComponent();

    

     //Enable stylus events and load pictures

     this.Loaded += (s, e) => { LoadPictures(); };

    

     //Register for mouse events

     MouseLeftButtonDown += ProcessDown;

     MouseMove += ProcessMove;

     MouseLeftButtonUp += ProcessUp;

     MouseWheel += ProcessMouseWheel;

}

Visual Basic

Public Sub New()

    InitializeComponent()

End Sub

注意: 在 Visual Basic 中,事件处理注册在事件处理程序声明中定义,使用 Handles 关键字。

6.            LoadPictures() 函数从用户的图片文件夹加载图片,并为所有图片创建一个 Picture 控件。它只在初始化画布之后才执行此操作。下面看一下 LoadPictures() 代码。

7.            现在看一下如何处理鼠标事件。

C#

private void ProcessDown(object sender, MouseButtonEventArgs args)

{

     _prevLocation = args.GetPosition(_canvas);

     _picture = FindPicture(_prevMouseLocation);

     BringPictureToFront(_picture);

}

Visual Basic

Private Sub ProcessDown(ByVal sender As Object, ByVal args As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown

    _prevLocation = args.GetPosition(_canvas)

    _picture = FindPicture(_prevLocation)

    BringPictureToFront(_picture)

End Sub

按下鼠标左键将启动一个新的图片拖动会话。首先我们必须获得相对于画布的指针位置。我们将此信息保存在 _prevLocation 数据成员中。

8.            下一步是在该位置找到一张图片。FindPicture() 函数利用 WPF VisualTree 点击测试功能来找到最顶层的图片。如果鼠标所在位置没有图片,则返回空值。

9.            BringPictureToFront() 将所选图片的 Z 轴次序设置在其他图片的最顶层。

此处理程序的处理结果是 _picture 数据成员“记住”了所选的图片,_prevLocation 获取鼠标位置的代码片段。我们看一下当鼠标移动时会发生什么情况:

C#

private void ProcessMove(object sender, MouseEventArgs args)

{

     if (args.LeftButton == MouseButtonState.Released || _picture == null)

         return;

     Point newLocation = args.GetPosition(_canvas);

     _picture.X += newLocation.X - _prevMouseLocation.X;

     _picture.Y += newLocation.Y - _prevMouseLocation.Y;

     _prevLocation = newLocation;

}

Visual Basic

Private Sub ProcessMove(ByVal sender As Object, ByVal args As MouseEventArgs) Handles Me.MouseMove

    If args.LeftButton = MouseButtonState.Released OrElse _picture Is Nothing Then Return

    Dim newLocation = args.GetPosition(_canvas)

    _picture.X += newLocation.X - _prevLocation.X

    _picture.Y += newLocation.Y - _prevLocation.Y

    _prevLocation = newLocation

End Sub

如果用户未按下鼠标左键或者未选择任何图片,该函数将不执行任何操作。否则,该函数将计算平移量并更新图片的 X 和 Y 属性。它还将更新 _prevLocation。

10.          我们需要注意的最后一个函数是 ProcessMouseWheel:

C#

private void ProcessMouseWheel(object sender, MouseWheelEventArgs args)

{

    Point location = args.GetPosition(_canvas);

    Picture picture = FindPicture(location);

    if (picture == null)

        return;

    BringPictureToFront(picture);

    double scalingFactor = 1 + args.Delta / 1000.0;

    picture.ScaleX *= scalingFactor;

    picture.ScaleY *= scalingFactor;

}

Visual Basic

Private Sub ProcessMouseWheel(ByVal sender As Object, ByVal args As MouseWheelEventArgs) Handles Me.MouseWheel

    Dim location = args.GetPosition(_canvas)

    Dim picture = FindPicture(location)

    If picture Is Nothing Then Return

    BringPictureToFront(picture)

    Dim scalingFactor = 1 + args.Delta / 1000.0

    picture.ScaleX *= scalingFactor

    picture.ScaleY *= scalingFactor

End Sub

此函数获取鼠标指针位置,找到该位置下的图片,将其呈现在最前面。然后它从鼠标滚轮偏移量中得到偏移系数。最后只需更新图片缩放比例。

               

任务 2 测试多点触摸硬件是否存在及其就绪情况

在本任务中,我们将开始编写多点触摸程序。尽管 WPF 3.5 不支持多点触摸(多点触摸事件和控件将包含在 WPF 4.0 中),但可以通过某种方式来在当前版本中使用多点触摸。为此,我们必须使用 Windows 7 Integration Library 示例。此集成库是一个示例,演示了如何在 .NET 代码中使用 Win32 本机 API。

注意: 可以从网址 https://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=WindowsTouch&DownloadId=5038  获取 Windows 7 Integration Library 示例。为了简单起见,这些库在 %TrainingKitInstallDir%\MultiTouch\Assets\Win7LibSample 下以实验资源的形式提供,请选择您想要使用的语言(C# 或 VB)。

1.            添加对 Windows7.Multitouch.dll 和 Windows7.Multitouch.WPF.dll 的引用。

2.            将以下代码添加到 MainWindow 构造函数中:

(代码片段 – MultiTouch – IsMultiTouchReady CSharp)

C#

if (!Windows7.Multitouch.TouchHandler.DigitizerCapabilities.IsMultiTouchReady)

{

    MessageBox.Show("Multitouch is not availible");

    Environment.Exit(1);

}

(代码片段 – MultiTouch – IsMultiTouchReady VB)

Visual Basic

If Not Windows7.Multitouch.TouchHandler.DigitizerCapabilities.IsMultiTouchReady Then

    MsgBox("Multitouch is not availible")

    Environment.Exit(1)

End If

3.            查看 TouchHandler.DigitizerCapabilities 的其他属性。

 

图 2

查看 TouchHandler.DigitizerCapabilities 属性

               

任务 3 **将鼠标事件替换为触摸事件  **

在本练习中,我们将删除鼠标事件并将其替换为触摸事件,以便使用我们的手指处理图片。

1.            将以下代码行添加到 MainWindow.xaml.cs 文件 (C#) 或 MainWindow.xaml.vb 文件 (Visual Basic) 开头:

C#

using Windows7.Multitouch;

using Windows7.Multitouch.WPF;

Visual Basic

Imports Windows7.Multitouch

Imports Windows7.Multitouch.WPF

2.            我们想要在 WPF 3.5 SP1 中实现多点触摸事件。为此,必须告诉系统以触笔事件的形式发出触摸事件。Windows 7 Integration Library 的 WPF Factory 类拥有一个函数来实现此功能,那就是 EnableStylusEvent。在 MainWindow Loaded 事件处理程序中添加对此函数的调用:

C#

public MainWindow()

{

    ...

    //Enable stylus events and load pictures

    this.Loaded += (s, e) => { Factory.EnableStylusEvents(this); LoadPictures(); };

    ...

Visual Basic

Private Sub Window_OnLoaded() Handles Me.Loaded

    Factory.EnableStylusEvents(Me)

    LoadPictures()

End Sub

3.            删除 ProcessMouseWheel 事件处理程序及相应的事件注册(我们将在稍后处理缩放)。

4.            (仅适用于 C# 用户)删除 MouseLeftButtonDown、MouseMove 和 MouseLeftButtonUp 的事件注册代码。MainWindow 构造函数应该类似于以下代码:

C#

public MainWindow()

{

    InitializeComponent();

    if (!Windows7.Multitouch.TouchHandler.DigitizerCapabilities.IsMultiTouchReady)

    {

        MessageBox.Show("Multitouch is not availible");

        Environment.Exit(1);

    }

    this.Loaded += (s, e) => { Factory.EnableStylusEvents(this); LoadPictures(); };

}

5.            更改以下事件处理程序的签名和代码:

注意:此事件处理程序的签名已经更改。我们使用StylusEventArgs 代替与鼠标相关的事件参数。

(代码片段 – MultiTouch – StylusEventHandlers CSharp)

C#

public void ProcessDown(object sender, StylusEventArgs args)

{

    _prevLocation = args.GetPosition(_canvas);

    _picture = FindPicture(_prevLocation);

    BringPictureToFront(_picture);

}

public void ProcessMove(object sender, StylusEventArgs args)

{

    if (_picture == null)

        return;

    Point newLocation = args.GetPosition(_canvas);

    _picture.X += newLocation.X - _prevLocation.X;

    _picture.Y += newLocation.Y - _prevLocation.Y;

    _prevLocation = newLocation;

}

public void ProcessUp(object sender, StylusEventArgs args)

{

    _picture = null;

}

(代码片段 – MultiTouch – StylusEventHandlers VB)

Visual Basic

Public Sub ProcessDown(ByVal sender As Object, ByVal args As StylusEventArgs)

    _prevLocation = args.GetPosition(_canvas)

    _picture = FindPicture(_prevLocation)

    BringPictureToFront(_picture)

End Sub

Public Sub ProcessMove(ByVal sender As Object, ByVal args As StylusEventArgs)

    If _picture Is Nothing Then Return

    Dim newLocation = args.GetPosition(_canvas)

    _picture.X += newLocation.X - _prevLocation.X

    _picture.Y += newLocation.Y - _prevLocation.Y

    _prevLocation = newLocation

End Sub

Public Sub ProcessUp(ByVal sender As Object, ByVal args As StylusEventArgs)

    _picture = Nothing

End Sub

6.            注册触笔事件。

C#

public MainWindow()

{

...

    //Register for stylus (touch) events

    StylusDown += ProcessDown;

    StylusUp += ProcessUp;

    StylusMove += ProcessMove;

}

Visual Basic

Public Sub ProcessDown(ByVal sender As Object, ByVal args As StylusEventArgs) Handles Me.StylusDown

...

End Sub

Public Sub ProcessMove(ByVal sender As Object, ByVal args As StylusEventArgs) Handles Me.StylusMove

...

End Sub

Public Sub ProcessUp(ByVal sender As Object, ByVal args As StylusEventArgs) Handles Me.StylusUp

...

End Sub

7.            编译并运行。使用手指代替鼠标!

注意: 如果尝试使用多个手指会发生什么情况?为什么?

任务 4 同时处理多张图片

在本任务中,我们将添加多点触摸支持。触摸屏幕的每个手指都会获得一个唯一的触摸 ID。只要这根手指继续触摸屏幕,系统就会将相同的触摸 ID 与该手指关联。当手指离开屏幕表面时,该触摸 ID 将被系统释放并可被硬件再次使用。在我们的示例中,当一根手指触摸图片时,应该将该手指的唯一触摸 ID 与该图片关联,直到该手指离开屏幕。如果两个或更多手指同时触摸屏幕,那么每个手指都可以操作相关的图片。

当使用 Stylus 事件作为触摸事件时,可以从 Stylus 事件参数中提取出触摸 ID:

C# | Visual Basic

args.StylusDevice.Id

WPF 将使用相关的 StylusDevice.Id(触摸 ID)不断为每个触摸屏幕的手指触发事件。

1.            我们需要同时跟踪多张图片。对于每张图片,触摸 ID、上一个位置与图片用户控件之间必须保持关联。我们将首先添加一个新的 PictureTracker 类:

注意:PictureTracker 类也在 %TrainingKitInstallDir%\MultiTouch\Assets\PictureHandling下以实验资源的形式提供,请选择您想要使用的语言(C# 或 VB)。

(代码片段 – MultiTouch – PictureTrackerClass CSharp)

C#

/// <summary>

/// Track a single picture

/// </summary>

class PictureTracker

{

       private Point _prevLocation;

       public Picture Picture { get; set; }

       public void ProcessDown(Point location)

       {

           _prevLocation = location;

       }

       public void ProcessMove(Point location)

       {

           Picture.X += location.X - _prevLocation.X;

           Picture.Y += location.Y - _prevLocation.Y;

           _prevLocation = location;

       }

       public void ProcessUp(Point location)

       {

           //Do Nothing, We might have another touch-id that is

           //still down

       }

}

(代码片段 – MultiTouch – PictureTrackerClass VB)

Visual Basic

''' <summary>

''' Track a single picture.

''' </summary>

Imports System.Windows

Class PictureTracker

    Private _prevLocation As Point

    Private _picture As Picture

    Public Property Picture() As Picture

        Get

            Return _picture

        End Get

        Set(ByVal value As Picture)

            _picture = value

        End Set

    End Property

    Public Sub ProcessDown(ByVal location As Point)

        _prevLocation = location

    End Sub

    Public Sub ProcessMove(ByVal location As Point)

        Picture.X += location.X - _prevLocation.X

        Picture.Y += location.Y - _prevLocation.Y

        _prevLocation = location

    End Sub

    Public Sub ProcessUp(ByVal location As Point)

        ' Do Nothing, We might have another touch-id that is.

        ' Still down.

    End Sub

End Class

2.            现在我们需要一个词典,以将活动的触摸 ID 映射到相应的 PictureTracker 实例。我们将创建一个 PictureTrackerManager 类来包含该词典并处理各种触摸事件。无论何时触发了触摸事件,PictureTrackerManager 都将尝试找到关联的 PictureTracker 实例并要求它处理该触摸事件。换言之,PictureTrackerManager 将获得触摸事件。它寻找作为实际事件目标的 PictureTracker 实例并将触摸事件分派给它。现在的问题是如何找到正确的 PictureTracker 实例。我们需要考虑一些不同的场景:

a.            发生 ProcessDown 事件时,有 3 种选择:

i.              手指触摸一个空位置。不会发生任何事件。

ii.             手指触摸新图片。必须创建一个新 PictureTracker 实例,必须在触摸 ID 映射中创建一个新条目。

iii.            第 2 个(或更多)手指触摸已经被跟踪的图片。我们必须将新的触摸 ID 与相同的 PictureTracker 实例相关联。

b.            发生 ProcessMove 事件时,有 2 种选择:

i.              手指的触摸 ID 未与一个 PictureTracker 相关联。不应该发生任何事件。

ii.             手指的触摸 ID 与一个 PictureTracker 关联。我们需要将事件转发给它。

c.             发生 ProcessUp 事件时,有 2 种选择:

i.              删除了一个手指触摸 ID,但是至少还存在一个相关的触摸 ID。我们需要从映射中删除此条目。

ii.             删除了最后一个相关的触摸 ID。我们需要从映射中删除该条目。图片跟踪器不再使用并且会被当作垃圾收集走。

3.            通过分析这些情形,我们可以定义 PictureTrackerManager 的设计条件:

a.            它必须拥有一个映射:触摸 ID PictureTracker

C#

private readonly Dictionary<int, PictureTracker> _pictureTrackerMap

Visual Basic

Private ReadOnly _pictureTrackerMap As Dictionary(Of Integer, PictureTracker)

b.            它必须使用 VisualTree 点击测试或通过在映射中查找来找到 PictureTracker

c.             它必须将事件转发给正确的 PictureTracker

4.            添加以下 PictureTrackerManager 类:

注意:PictureTrackerManager 类也以实验资产的形式在 %TrainingKitInstallDir%\MultiTouch\Assets\PictureHandling 下提供,请选择您想要使用的语言(C# 或 VB)。

(代码片段 – MultiTouch – PictureTrackerManagerClass CSharp)

C#

class PictureTrackerManager

{

    //Map between touch ids and picture trackers

    private readonly Dictionary<int, PictureTracker> _pictureTrackerMap = new Dictionary<int, PictureTracker>();

    private readonly Canvas _canvas;

    public PictureTrackerManager(Canvas canvas)

    {

        _canvas = canvas;

    }

    public void ProcessDown(object sender, StylusEventArgs args)

    {

        Point location = args.GetPosition(_canvas);

        PictureTracker pictureTracker = GetPictureTracker(args.StylusDevice.Id, location);

        if (pictureTracker == null)

            return;

        pictureTracker.ProcessDown(location);

    }

    public void ProcessUp(object sender, StylusEventArgs args)

    {

        Point location = args.GetPosition(_canvas);

        PictureTracker pictureTracker = GetPictureTracker(args.StylusDevice.Id);

        if (pictureTracker == null)

            return;

        pictureTracker.ProcessUp(location);

        _pictureTrackerMap.Remove(args.StylusDevice.Id);

    }

    public void ProcessMove(object sender, StylusEventArgs args)

    {

        PictureTracker pictureTracker = GetPictureTracker(args.StylusDevice.Id);

        if (pictureTracker == null)

            return;

        Point location = args.GetPosition(_canvas);

        pictureTracker.ProcessMove(location);

    }

    private PictureTracker GetPictureTracker(int touchId)

    {

        PictureTracker pictureTracker = null;

        _pictureTrackerMap.TryGetValue(touchId, out pictureTracker);

        return pictureTracker;

    }

    private PictureTracker GetPictureTracker(int touchId, Point location)

    {

        PictureTracker pictureTracker;

        //See if we already track the picture with the touchId

        if (_pictureTrackerMap.TryGetValue(touchId, out pictureTracker))

            return pictureTracker;

        //Get the picture under the touch location

        Picture picture = FindPicture(location);

        if (picture == null)

            return null;

        //See if we track the picture with other ID

        pictureTracker = (from KeyValuePair<int, PictureTracker> entry in _pictureTrackerMap

                           where entry.Value.Picture == picture

                           select entry.Value).FirstOrDefault();

        //First time

        if (pictureTracker == null)

        {

            //create new

            pictureTracker = new PictureTracker();

            pictureTracker.Picture = picture;

            BringPictureToFront(picture);

        }

        //remember the corelation between the touch id and the picture

        _pictureTrackerMap[touchId] = pictureTracker;

         return pictureTracker;

    }

    /// <summary>

    /// Find the picture in the touch location

    /// </summary>

    /// <param name="pointF">touch location</param>

    /// <returns>The picture or null if no picture exists in the touch

    /// location</returns>

    private Picture FindPicture(Point location)

    {

        HitTestResult result = VisualTreeHelper.HitTest(_canvas, location);

        if (result == null)

            return null;

        Image image = result.VisualHit as Image;

         if (image == null)

            return null;

         return image.Parent as Picture;

    }

    private void BringPictureToFront(Picture picture)

    {

        if (picture == null)

            return;

        var children = (from UIElement child in _canvas.Children

                        where child != picture

                        orderby Canvas.GetZIndex(child)

                        select child).ToArray();

        for (int i = 0; i < children.Length; ++i)

        {

            Canvas.SetZIndex(children[i], i);

        }

        Canvas.SetZIndex(picture, children.Length);

    }

}

(代码片段 – MultiTouch – PictureTrackerManagerClass VB)

Visual Basic

Imports System.Windows

Imports System.Windows.Controls

Class PictureTrackerManager

    ' Map between touch ids and picture trackers

    Private ReadOnly _pictureTrackerMap As New Dictionary(Of Integer, PictureTracker)

    Private ReadOnly _canvas As Canvas

    Public Sub New(ByVal canvas As Canvas)

        _canvas = canvas

    End Sub

    Public Sub ProcessDown(ByVal sender As Object, ByVal args As StylusEventArgs)

        Dim location = args.GetPosition(_canvas)

        Dim pictureTracker = GetPictureTracker(args.StylusDevice.Id, location)

        If pictureTracker Is Nothing Then Return

        pictureTracker.ProcessDown(location)

    End Sub

    Public Sub ProcessUp(ByVal sender As Object, ByVal args As StylusEventArgs)

        Dim location = args.GetPosition(_canvas)

        Dim pictureTracker = GetPictureTracker(args.StylusDevice.Id)

        If pictureTracker Is Nothing Then Return

        pictureTracker.ProcessUp(location)

        _pictureTrackerMap.Remove(args.StylusDevice.Id)

    End Sub

    Public Sub ProcessMove(ByVal sender As Object, ByVal args As StylusEventArgs)

        Dim pictureTracker = GetPictureTracker(args.StylusDevice.Id)

        If pictureTracker Is Nothing Then Return

        Dim location = args.GetPosition(_canvas)

        pictureTracker.ProcessMove(location)

    End Sub

    Private Function GetPictureTracker(ByVal touchId As Integer) As PictureTracker

        Dim pictureTracker As PictureTracker = Nothing

        _pictureTrackerMap.TryGetValue(touchId, pictureTracker)

        Return pictureTracker

    End Function

    Private Function GetPictureTracker(ByVal touchId As Integer, ByVal location As Point) As PictureTracker

        Dim pictureTracker As PictureTracker = Nothing

        ' See if we already track the picture with the touchId

        If _pictureTrackerMap.TryGetValue(touchId, pictureTracker) Then Return pictureTracker

        ' Get the picture under the touch location

        Dim picture = FindPicture(location)

        If picture Is Nothing Then Return Nothing

        ' See if we track the picture with other ID

        pictureTracker = (From entry In _pictureTrackerMap _

                          Where entry.Value.Picture Is picture _

                          Select entry.Value).FirstOrDefault()

        ' First time

        If pictureTracker Is Nothing Then

            ' Create new

            pictureTracker = New PictureTracker()

            pictureTracker.Picture = picture

            BringPictureToFront(picture)

        End If

        ' Remember the corelation between the touch id and the picture

        _pictureTrackerMap(touchId) = pictureTracker

        Return pictureTracker

    End Function

    ''' <summary>

    ''' Find the picture in the touch location

    ''' </summary>

    ''' <param name="pointF">touch location</param>

    ''' <returns>The picture or null if no picture exists in the touch

    ''' location</returns>

    Private Function FindPicture(ByVal location As Point) As Picture

        Dim result = VisualTreeHelper.HitTest(_canvas, location)

        If result Is Nothing Then Return Nothing

        Dim image = TryCast(result.VisualHit, Image)

        If image Is Nothing Then Return Nothing

        Return TryCast(image.Parent, Picture)

    End Function

    Private Sub BringPictureToFront(ByVal picture As Picture)

        If picture Is Nothing Then Return

        Dim children = (From child In _canvas.Children _

                        Where child IsNot picture _

                        Order By Canvas.GetZIndex(child) _

                        Select child).ToArray()

        For i = 0 To children.Length - 1

            Canvas.SetZIndex(children(i), i)

        Next i

        Canvas.SetZIndex(picture, children.Length)

    End Sub

End Class

5.            将以下字段声明添加到 MainWindow 类的开头:

C#

private readonly PictureTrackerManager _pictureTrackerManager;

Visual Basic

Private ReadOnly _pictureTrackerManager As PictureTrackerManager

6.            修改 MainWindow 构造函数:

a.            在调用 InitializeComponent() 之后,添加管理器初始化:

C#

_pictureTrackerManager = new PictureTrackerManager(_canvas);

Visual Basic

_pictureTrackerManager = New PictureTrackerManager(_canvas)

b.            更改触笔事件注册代码

(代码片段 – MultiTouch – PictureTrackerManagerEventHandlers CSharp)

C#

//Register for stylus (touch) events

StylusDown += _pictureTrackerManager.ProcessDown;

StylusUp += _pictureTrackerManager.ProcessUp;

StylusMove += _pictureTrackerManager.ProcessMove;

(代码片段 – MultiTouch – PictureTrackerManagerEventHandlers VB)

Visual Basic

' Register for stylus (touch) events

AddHandler Me.StylusDown, AddressOf _pictureTrackerManager.ProcessDown

AddHandler Me.StylusMove, AddressOf _pictureTrackerManager.ProcessMove

AddHandler Me.StylusUp, AddressOf _pictureTrackerManager.ProcessUp

7.            从 MainWindow 类删除 ProcessDown、ProcessMove 和 ProcessUp 事件处理程序。这里将不再需要它们,因为它们现在已包含在 PictureTrackerManager 类中。

8.            编译并运行。尝试同时抓取多张图片。尝试使用多个手指抓取一张图片。发生了什么情况?为什么?

               

任务 5 使用多点触摸操作处理图片

到目前为止,使用触摸事件处理图片与使用鼠标功能并没有太大区别。在本任务中,我们将:

•               添加使用多个手指操作图片的能力

•               同时平移、缩放和旋转图片

•               同时操作多张图片

我们已经知道如何将正确的事件分派给相应的 PictureTracker,但我们还不知道如何决定在发生多个事件之后需要采取的操作。这正是 Windows 7 多点触摸机制的用武之地。它拥有一个操作处理器来使用触摸 ID 事件并生成合适的操作事件。您只需实例化一个操作处理器,注册其事件,并为它提供触摸 ID + 位置事件对。 

操作处理器是一个 COM 对象。要在 .NET 中使用它,可以使用 Windows 7 Integration Library 示例。ManipulationProcessor .NET 包装器类构造函数获得一个枚举值,该值告诉它要报告哪些操作。在我们的示例中,我们希望报告所有操作。该处理器有 3 个事件:ManipulationStarted、ManipulationCompleted 和 ManipulationDelta。ManipulationDelta 是我们所关注的事件。它提供了平移、旋转和缩放的偏移量。

1.            更改整个 PictureTracker 类。

(代码片段 – MultiTouch – PictureTrackerManipulationProcessorClass CSharp)

C#

class PictureTracker

{

    private readonly ManipulationProcessor _processor =

        new ManipulationProcessor(ProcessorManipulations.ALL);

    public PictureTracker()

    {

        _processor.ManipulationStarted += (s, e) =>

        {

            System.Diagnostics.Trace.WriteLine("Manipulation has started: " + Picture.ImagePath);

        };

        _processor.ManipulationCompleted += (s, e) =>

        {

            System.Diagnostics.Trace.WriteLine("Manipulation has completed: " + Picture.ImagePath);

        };

        _processor.ManipulationDelta += ProcessManipulationDelta;

    }

    public Picture Picture { get; set; }

    public void ProcessDown(int id, Point location)

    {

        _processor.ProcessDown((uint)id, location.ToDrawingPointF());

    }

    public void ProcessMove(int id, Point location)

    {

        _processor.ProcessMove((uint)id, location.ToDrawingPointF());

    }

    public void ProcessUp(int id, Point location)

    {

        _processor.ProcessUp((uint)id, location.ToDrawingPointF());

    }

    //Update picture state

    private void ProcessManipulationDelta(object sender, ManipulationDeltaEventArgs e)

    {

        if (Picture == null)

            return;

        Picture.X += e.TranslationDelta.Width;

        Picture.Y += e.TranslationDelta.Height;

        Picture.Angle += e.RotationDelta * 180 / Math.PI;

        Picture.ScaleX *= e.ScaleDelta;

        Picture.ScaleY *= e.ScaleDelta;

    }

}

(代码片段 – MultiTouch – PictureTrackerManipulationProcessorClass VB)

Visual Basic

Class PictureTracker

    Private _picture As Picture

    Public Property Picture() As Picture

        Get

            Return _picture

        End Get

        Set(ByVal value As Picture)

            _picture = value

        End Set

    End Property

    Private WithEvents _processor As New ManipulationProcessor(ProcessorManipulations.ALL)

    Public Sub New()

    End Sub

    Private Sub Processor_OnManipulationStarted() Handles _processor.ManipulationStarted

        System.Diagnostics.Trace.WriteLine("Manipulation has started: " & Picture.ImagePath)

    End Sub

    Private Sub Processor_OnManipulationCompleted() Handles _processor.ManipulationCompleted

        System.Diagnostics.Trace.WriteLine("Manipulation has completed: " & Picture.ImagePath)

    End Sub

    ' Update picture state

    Private Sub ProcessManipulationDelta(ByVal sender As Object, ByVal e As ManipulationDeltaEventArgs) Handles _processor.ManipulationDelta

        If Picture Is Nothing Then Return

        Picture.X += e.TranslationDelta.Width

        Picture.Y += e.TranslationDelta.Height

        Picture.Angle += e.RotationDelta * 180 / Math.PI

        Picture.ScaleX *= e.ScaleDelta

        Picture.ScaleY *= e.ScaleDelta

    End Sub

    Public Sub ProcessDown(ByVal id As Integer, ByVal location As Point)

        _processor.ProcessDown(CUInt(id), location.ToDrawingPointF())

    End Sub

    Public Sub ProcessMove(ByVal id As Integer, ByVal location As Point)

        _processor.ProcessMove(CUInt(id), location.ToDrawingPointF())

    End Sub

    Public Sub ProcessUp(ByVal id As Integer, ByVal location As Point)

        _processor.ProcessUp(CUInt(id), location.ToDrawingPointF())

    End Sub

End Class

2.            将以下命名空间指令添加到 PictureTracker 类中:

C#

using Windows7.Multitouch.Manipulation;

using Windows7.Multitouch.WPF;

Visual Basic

Imports Windows7.Multitouch.Manipulation

Imports Windows7.Multitouch.WPF

注意: 通过添加此命名空间,可以使用 ManipulatorProcessor 类和 System.Windows.Point 扩展方法 ToDrawingPointF。

3.            我们实例化了一个新的 ManipulationProcessor,注册了事件处理器,而且最重要的是,通过更新图片用户控件处理了 ManipulationDelta 事件。现在我们需要对 PictureTrackerManager 事件处理代码稍作修改,并转发触摸 ID 和触摸位置。ManipulationProcessor 需要将触摸 ID 作为操作流程的输入。更改 PictureTrackerManager 中的以下代码:

C#

public void ProcessDown(object sender, StylusEventArgs args)

{

    Point location = args.GetPosition(_canvas);

    PictureTracker pictureTracker = GetPictureTracker(args.StylusDevice.Id, location);

    if (pictureTracker == null)

        return;

    pictureTracker.ProcessDown(args.StylusDevice.Id, location);

}

public void ProcessUp(object sender, StylusEventArgs args)

{

    Point location = args.GetPosition(_canvas);

    PictureTracker pictureTracker = GetPictureTracker(args.StylusDevice.Id);

    if (pictureTracker == null)

        return;

    pictureTracker.ProcessUp(args.StylusDevice.Id, location);

    _pictureTrackerMap.Remove(args.StylusDevice.Id);

}

public void ProcessMove(object sender, StylusEventArgs args)

{

    PictureTracker pictureTracker = GetPictureTracker(args.StylusDevice.Id);

    if (pictureTracker == null)

         return;

     Point location = args.GetPosition(_canvas);

     pictureTracker.ProcessMove(args.StylusDevice.Id, location);

}

Visual Basic

Public Sub ProcessDown(ByVal sender As Object, ByVal args As StylusEventArgs)

    Dim location = args.GetPosition(_canvas)

    Dim pictureTracker = GetPictureTracker(args.StylusDevice.Id, location)

    If pictureTracker Is Nothing Then Return

    pictureTracker.ProcessDown(args.StylusDevice.Id, location)

End Sub

Public Sub ProcessUp(ByVal sender As Object, ByVal args As StylusEventArgs)

    Dim location = args.GetPosition(_canvas)

    Dim pictureTracker = GetPictureTracker(args.StylusDevice.Id)

    If pictureTracker Is Nothing Then Return

    pictureTracker.ProcessUp(args.StylusDevice.Id, location)

    _pictureTrackerMap.Remove(args.StylusDevice.Id)

End Sub

Public Sub ProcessMove(ByVal sender As Object, ByVal args As StylusEventArgs)

    Dim pictureTracker = GetPictureTracker(args.StylusDevice.Id)

    If pictureTracker Is Nothing Then Return

    Dim location = args.GetPosition(_canvas)

    pictureTracker.ProcessMove(args.StylusDevice.Id, location)

End Sub

4.            编译并运行代码。尝试同时操作多张图片。

               

任务 6 添加 PictureTracker 缓存

当用户首次触摸一张图片时,应用程序创建一个新 PictureTracker 实例,该实例然后创建 ManipulationProcessor COM 对象。只要用户移开触摸该图片的最后一个指头(触摸 ID),PictureTracker 实例就会被当作垃圾收集,进而释放底层 COM 对象。分析常见的应用程序使用情形就会发现,只有少数图片可能被同时操作。据此可以得出结论:我们需要 PictureTracker 实例的一个缓存。该缓存将包含空闲的 PictureTracker 实例。当(发生 ProcessDown 事件时)需要新 PictureTracker 实例时,我们将首先尝试从缓存拉取实例,只有当缓存为空时才生成新实例。当完成对图片的操作时,我们将 PictureTracker 实例移入缓存。因为 ManipulationCompleted 是一个 ManipulationProcessor 事件,所以我们将要求 PictureTracker 处理该事件并将其转发给 PictureTrackerManager。这需要一个从 PictureTracker 到它的 PictureTrackerManager 的新引用(我们使用构造函数来传递该引用)。

1.            将堆栈数据成员添加到 PictureTrackerManager 类的开头:

C#

class PictureTrackerManager

{

    //Cache for re-use of picture trackers

    private readonly Stack<PictureTracker> _pictureTrackers = new Stack<PictureTracker>();

...

Visual Basic

Class PictureTrackerManager

    ' Cache for re-use of picture trackers

    Private ReadOnly _pictureTrackers As New Stack(Of PictureTracker)()

...

2.            更改 GetPictureTracker() 函数。我们需要使用缓存,还需要将此引用传递给 PictureTracker 构造函数:

C#

private PictureTracker GetPictureTracker(int touchId, Point location)

{

...

   //First time

   if (pictureTracker == null)

    {

        //take from stack

        if (_pictureTrackers.Count > 0)

            pictureTracker = _pictureTrackers.Pop();

        else //create new

            pictureTracker = new PictureTracker(this);

       

        pictureTracker.Picture = picture;

        BringPictureToFront(picture);

    }

...

}

Visual Basic

Private Function GetPictureTracker(ByVal touchId As Integer, ByVal location As Point) As PictureTracker

...   

    ' First time

    If pictureTracker Is Nothing Then

        ' take from stack

        If _pictureTrackers.Count > 0 Then

            pictureTracker = _pictureTrackers.Pop()

        Else ' create new

            pictureTracker = New PictureTracker(Me)

        End If

        pictureTracker.Picture = picture

        BringPictureToFront(picture)

    End If

...

End Function

3.        添加一个逻辑,以在操作完成时将 PictureTracker 实例推回堆栈中。将以下代码粘贴到 PictureTrackerManager 类中。

C#

//Manipulation is completed, we can reuse the object

public void Completed(PictureTracker pictureTracker)

{

    pictureTracker.Picture = null;

    _pictureTrackers.Push(pictureTracker);

}

Visual Basic

' Manipulation is completed, we can reuse the object

Public Sub Completed(ByVal pictureTracker As PictureTracker)

    pictureTracker.Picture = Nothing

    _pictureTrackers.Push(pictureTracker)

End Sub

4.            现在需要更改 PictureTracker 类,使其适应 PictureTrackerManager 中的代码更改。

a.            将 PictureTrackerManager 实例获取到构造函数中,然后存储它。

C#

class PictureTracker

{

    private readonly ManipulationProcessor _processor =

        new ManipulationProcessor(ProcessorManipulations.ALL);

    private readonly PictureTrackerManager _pictureTrackerManager;

    public PictureTracker(PictureTrackerManager pictureTrackerManager)

    {

        _pictureTrackerManager = pictureTrackerManager;

...

Visual Basic

Class PictureTracker

...

    Private ReadOnly _pictureTrackerManager As PictureTrackerManager

    Private WithEvents _processor As New ManipulationProcessor(ProcessorManipulations.ALL)

    Public Sub New(ByVal pictureTrackerManager As PictureTrackerManager)

        _pictureTrackerManager = pictureTrackerManager

    End Sub

...

b.            在 ManipulationCompleted 事件中调用 PictureTrackerManager.Completed 函数:

C#

public PictureTracker(PictureTrackerManager pictureTrackerManager)

{

    _pictureTrackerManager = pictureTrackerManager;

    _processor.ManipulationCompleted += (s, e) =>

    {

        System.Diagnostics.Trace.WriteLine("Manipulation has completed: " + Picture.ImagePath);

        _pictureTrackerManager.Completed(this);

    };

...

Visual Basic

Private Sub Processor_OnManipulationCompleted() Handles _processor.ManipulationCompleted

    System.Diagnostics.Trace.WriteLine("Manipulation has completed: " & Picture.ImagePath)

    _pictureTrackerManager.Completed(Me)

End Sub

5.            编译并运行!

               

任务 7 添加惯性

只剩最后一项任务了。使用缩放、平移和旋转操作可以提供一种自然的用户体验。在实际生活中,当推动一个物体,然后松开手时,该物体会继续移动,直到因为无法克服摩擦力而停止。可以使用 Inertia 让我们的图片对象拥有相同的行为。Windows 7 多点触摸子系统提供了一个 InertiaProcessor COM 对象。InertiaProcessor 可以发起与 ManipulationProcessor 相同的操作事件。Windows 7 Integration Library 示例提供了一个包装器,它将操作处理器和惯性处理器捆绑在一起。ManipulationInertiaProcessor 可以替代 ManipulationProcessor 并提供额外的 InertiaProcessor 属性来公开 InertiaProcessor 功能。要发起更多事件,ManipulationInertiaProcessor 需要一个计时器。为了克服线程的 UI 相似性问题,我们最好拥有一个基于 GUI 的计时器。Windows 7 Integration Library 可以为我们创建这样的计时器。

当用户的最后一个手指离开图片对象时,ManipulationInertiaProcessor 会发起 OnBeforeInertia 事件。在这里设置 Inertia 开始参数。可以选择一个默认的开始速度,或者跟踪当前的对象速度并从中提取出速度数字。

1.            我们想要跟踪对象的平移、旋转和缩放速度。将以下类添加到 PictureTracker 类中:

(代码片段 – MultiTouch – InertiaParamClass CSharp)

C#

//Keep track of object velocities

private class InertiaParam

{

    public VectorF InitialVelocity { get; set; }

    public float InitialAngularVelocity { get; set; }

    public float InitialExpansionVelocity { get; set; }

    public System.Diagnostics.Stopwatch _stopwatch = new System.Diagnostics.Stopwatch();

    public void Reset()

    {

        InitialVelocity = new VectorF(0, 0);

        InitialAngularVelocity = 0;

        InitialExpansionVelocity = 0;

        _stopwatch.Reset();

        _stopwatch.Start();

    }

    public void Stop()

    {

        _stopwatch.Stop();

    }

    //update velocities, velocity = distance/time

    public void Update(ManipulationDeltaEventArgs e, float history)

    {

        float elappsedMS = (float)_stopwatch.ElapsedMilliseconds;

        if (elappsedMS == 0)

            elappsedMS = 1;

        InitialVelocity = InitialVelocity * history + ((VectorF)e.TranslationDelta * (1F - history)) / elappsedMS;

        InitialAngularVelocity = InitialAngularVelocity * history + (e.RotationDelta * (1F - history)) / elappsedMS;

        InitialExpansionVelocity = InitialExpansionVelocity * history + (e.ExpansionDelta * (1F - history)) / elappsedMS;

        _stopwatch.Reset();

        _stopwatch.Start();

    }

}

(代码片段 – MultiTouch – InertiaParamClass VB)

Visual Basic

' Keep track of object velocities.

Private Class InertiaParam

    Private _initialVelocity As VectorF

    Public Property InitialVelocity() As VectorF

        Get

            Return _initialVelocity

        End Get

        Set(ByVal value As VectorF)

            _initialVelocity = value

        End Set

    End Property

    Private _initialAngularVelocity As Single

    Public Property InitialAngularVelocity() As Single

        Get

            Return _initialAngularVelocity

        End Get

        Set(ByVal value As Single)

            _initialAngularVelocity = value

        End Set

    End Property

    Private _initialExpansionVelocity As Single

    Public Property InitialExpansionVelocity() As Single

        Get

            Return _initialExpansionVelocity

        End Get

        Set(ByVal value As Single)

            _initialExpansionVelocity = value

        End Set

    End Property

    Public _stopwatch As New System.Diagnostics.Stopwatch()

    Public Sub Reset()

        InitialVelocity = New VectorF(0, 0)

        InitialAngularVelocity = 0

        InitialExpansionVelocity = 0

        _stopwatch.Reset()

        _stopwatch.Start()

    End Sub

    Public Sub [Stop]()

        _stopwatch.Stop()

    End Sub

    'update velocities, velocity = distance/time

    Public Sub Update(ByVal e As ManipulationDeltaEventArgs, ByVal history As Single)

        Dim elappsedMS = CSng(_stopwatch.ElapsedMilliseconds)

        If elappsedMS = 0 Then elappsedMS = 1

        InitialVelocity = InitialVelocity * history + (CType(e.TranslationDelta, VectorF) * (1.0F - history)) / elappsedMS

        InitialAngularVelocity = InitialAngularVelocity * history + (e.RotationDelta * (1.0F - history)) / elappsedMS

        InitialExpansionVelocity = InitialExpansionVelocity * history + (e.ExpansionDelta * (1.0F - history)) / elappsedMS

        _stopwatch.Reset()

        _stopwatch.Start()

    End Sub

End Class

2.            将 OnBeforeInertia() 事件处理程序添加到 PictureTracker 类中:

(代码片段 – MultiTouch – OnBeforeInertia CSharp)

C#

//Fingers removed, start inertia

void OnBeforeInertia(object sender, BeforeInertiaEventArgs e)

{

    //Tell the tracker manager that the user removed the fingers

    _pictureTrackerManager.InInertia(this);

    _processor.InertiaProcessor.InertiaTimerInterval = 15;

    _processor.InertiaProcessor.MaxInertiaSteps = 500;

    _processor.InertiaProcessor.InitialVelocity = _inertiaParam.InitialVelocity;

    _processor.InertiaProcessor.DesiredDisplacement = _inertiaParam.InitialVelocity.Magnitude * 250;

    _processor.InertiaProcessor.InitialAngularVelocity = _inertiaParam.InitialAngularVelocity * 20F / (float)Math.PI;

    _processor.InertiaProcessor.DesiredRotation = Math.Abs(_inertiaParam.InitialAngularVelocity *

    _processor.InertiaProcessor.InertiaTimerInterval * 540F / (float)Math.PI);

    _processor.InertiaProcessor.InitialExpansionVelocity = _inertiaParam.InitialExpansionVelocity * 15;

    _processor.InertiaProcessor.DesiredExpansion = Math.Abs(_inertiaParam.InitialExpansionVelocity * 4F);

}

    (代码片段 – MultiTouch – OnBeforeInertia VB)

Visual Basic

' Fingers removed, start inertia

Private Sub OnBeforeInertia(ByVal sender As Object, ByVal e As BeforeInertiaEventArgs)

    'Tell the tracker manager that the user removed the fingers

    _pictureTrackerManager.InInertia(Me)

    _processor.InertiaProcessor.InertiaTimerInterval = 15

    _processor.InertiaProcessor.MaxInertiaSteps = 500

    _processor.InertiaProcessor.InitialVelocity = _inertiaParam.InitialVelocity

    _processor.InertiaProcessor.DesiredDisplacement = _inertiaParam.InitialVelocity.Magnitude * 250

    _processor.InertiaProcessor.InitialAngularVelocity = _inertiaParam.InitialAngularVelocity * 20.0F / CSng(Math.PI)

    _processor.InertiaProcessor.DesiredRotation = Math.Abs(_inertiaParam.InitialAngularVelocity * _processor.InertiaProcessor.InertiaTimerInterval * 540.0F / CSng(Math.PI))

    _processor.InertiaProcessor.InitialExpansionVelocity = _inertiaParam.InitialExpansionVelocity * 15

    _processor.InertiaProcessor.DesiredExpansion = Math.Abs(_inertiaParam.InitialExpansionVelocity * 4.0F)

End Sub

3.            更改 PictureTracker 类,创建 ManipulationInertiaProcessor 并注册 OnBeforeInertia 事件:

C#

/// <summary>

/// Track a single picture

/// </summary>

class PictureTracker

{

...

    //Calculate the Inertia start velocity

    private readonly InertiaParam _inertiaParam = new InertiaParam();

    private readonly ManipulationInertiaProcessor _processor = new ManipulationInertiaProcessor(ProcessorManipulations.ALL, Factory.CreateTimer());

    public PictureTracker(PictureTrackerManager pictureTrackerManager)

    {

        _pictureTrackerManager = pictureTrackerManager;

        //Start inertia velocity calculations

        _processor.ManipulationStarted += (s, e) =>

        {

            _inertiaParam.Reset();

        };

        //All completed, inform the tracker manager that the current tracker

        //can be reused

        _processor.ManipulationCompleted += (s, e) =>

        {

            _inertiaParam.Stop();

            pictureTrackerManager.Completed(this);

        };

        _processor.ManipulationDelta += ProcessManipulationDelta;

        _processor.BeforeInertia += OnBeforeInertia;

    }

...

Visual Basic

Class PictureTracker

...

    Private ReadOnly _pictureTrackerManager As PictureTrackerManager

    'Calculate the Inertia start velocity

    Private ReadOnly _inertiaParam As New InertiaParam()

    Private WithEvents _processor As New ManipulationInertiaProcessor(ProcessorManipulations.ALL, Factory.CreateTimer())

    Public Sub New(ByVal pictureTrackerManager As PictureTrackerManager)

        _pictureTrackerManager = pictureTrackerManager

    End Sub

    Private Sub Processor_OnManipulationStarted() Handles _processor.ManipulationStarted

        _inertiaParam.Reset()

    End Sub

    Private Sub Processor_OnManipulationCompleted() Handles _processor.ManipulationCompleted

        _inertiaParam.Stop()

        _pictureTrackerManager.Completed(Me)

    End Sub

...

    Private Sub OnBeforeInertia(ByVal sender As Object, ByVal e As BeforeInertiaEventArgs) Handles _processor.BeforeInertia

    ...

    End Sub

...

4.        我们还需要更改 PictureTrackerManager。在新的条件下,图片可能由惯性处理器使用,即使没有手指在触摸该对象。我们需要在操作完成时立即从映射中删除触摸 ID,但是只有在惯性处理器使图片完全停止时,我们才能够重用 PictureTracker 对象。将 InInertia() 函数添加到 PictureTrackerManager 类中:

(代码片段 – MultiTouch – InInertia CSharp)

C#

//We remove the touchID from the tracking map since the fingers are

//no longer touching the picture

public void InInertia(PictureTracker pictureTracker)

{

    //remove all touch id from the map

    foreach (int id in

        (from KeyValuePair<int, PictureTracker> entry in _pictureTrackerMap

         where entry.Value == pictureTracker

         select entry.Key).ToList())

    {

        _pictureTrackerMap.Remove(id);

    }

}

(代码片段 – MultiTouch –InInertia VB)

Visual Basic

' We remove the touchID from the tracking map since the fingers are

' no longer touching the picture

Public Sub InInertia(ByVal pictureTracker As PictureTracker)

    ' remove all touch id from the map

    For Each id In (From entry In _pictureTrackerMap _

                               Where entry.Value Is pictureTracker _

                               Select entry.Key).ToList()

        _pictureTrackerMap.Remove(id)

    Next id

End Sub

5.            编译并运行。尝试将图片拉出屏幕。试验各种 Inertia 参数,看它们如何更改图片行为。

               

小结

本实验改进了一个基于鼠标的简单图片处理应用程序,将它升级成了类似于 Surface 的成熟的图片操作应用程序。您了解了如何检查是否存在多点触摸硬件,还看到了 Manipulation Processor 的魔力并在实验最后阶段添加了 Inertia 功能。

祝您实验愉快!