程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Windows 7開發:多點觸摸 - 管理(動手實驗)(上)

Windows 7開發:多點觸摸 - 管理(動手實驗)(上)

編輯:關於.NET

概述

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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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。

注意 : 可以從網址 http://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.IsMultiTouchRead y)
{
    MessageBox.Show("Multitouch is not  availible");
    Environment.Exit(1);
}

(代 碼片段 – MultiTouch – IsMultiTouchReady VB)

Visual Basic

If Not  Windows7.Multitouch.TouchHandler.DigitizerCapabilities.IsMultiTouchRead y 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.IsMultiTouchRead y)
    {
        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);
    }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved