本文章是由機器翻譯。

觸控指法

探索 Silverlight 中的多點觸控支援

Charles Petzold

下載範例程式碼

每當我瀏覽過自然歷程記錄中紐約市美國博物館我永遠使要放靈長類動物大廳點。 大型選取範圍的骷髏和填充的 specimens,使用走廊呈現的靈長類動物順序的進化 panorama — 動物,範圍從微小的樹狀目錄 shrews、 靈長類和透過黑猩猩、 大王 apes 及人類的 marmosets 的大小。

什麼 leaps 這個展示區從是顯著的特徵通用於所有的靈長類動物:骨那隻手包括 opposable 捲動方塊的結構。 接點和允許我們的祖系和遠方的親人,瞭解並爬樹狀目錄分支的數字相同的排列方式可讓我們指定操作我們,世界並建置事情。 我們手中很小的靈長類動物數以百萬計的多年前,數萬腳印可能會有其出處,但它們也是中是什麼使我們確定人類的主要因素。

它是我們到達 instinctively 點時,或甚至碰觸到電腦螢幕上的物件任何奇觀嗎?

以回應此人為想要將我們手指帶入更密切的連線,與電腦,我們輸入的裝置有也被發展。 滑鼠是了不起的選取和拖曳,但 hopeless 手繪多邊形草擬或手寫。 Tablet 手寫筆可讓我們撰寫,但是通常感覺怪怪的自動縮放或移動。 觸控式螢幕從 ATM 和博物館展示亭熟悉,但通常限制於簡單指向及按下。

我認為稱為 「 multi-touch 」 代表大閏向前技術。 當名 multi-touch 超過偵測到多個手指過去的觸控式螢幕,會龐大不同類型的移動與傳達透過螢幕的筆勢。 multi-touch 已經發展從的過去,觸控導向的輸入裝置,但同時提供建議本質上不同的輸入的典範。

multi-touch 可能已被電視新聞廣播上由常駐 meteorologist 或 pundit 操作的大螢幕上的地圖與最明顯的。 探索 Microsoft multi-touch 幾種方式 — 從咖啡資料表大小 Microsoft 曲面圖電腦到小型裝置像 Zune HD — 和技術變得相當標準上也 smartphones。

雖然 Microsoft 曲面圖可以回應許多同時手指 (並且甚至包含內部相機若要檢視的物件放置在玻璃上),最其他 multi-touch 裝置是限制為一個不連續的數字。 許多回應只有兩個手指 — 或接觸點,因為呼叫。 (我將會使用手指與相當 synonymously 接觸點)。但 synergy 位於以下工作:在 [電腦] 畫面兩個手指是超過兩次為強大做為其中一個。

觸控式的兩個點的限制是 multi-touch 變得可用最近的桌上型 PC 和膝上型電腦,以及自訂的 Acer 渴望 1420P 筆記型電腦散發給出席者 Microsoft 專業開發人員會議 (PDC) 在最後一個十一月的監視器的特性 — 通常稱為 PDC 膝上型電腦。 散發的 PDC 膝上型電腦提供撰寫多-touch 感知的應用程式開發人員有上千個唯一機會。

PDC 膝上型電腦是我用來探討在 Silverlight 3 下的 multi-touch 支援機器。

Silverlight 事件和類別

multi-touch 支援變得標準各種 Windows API 和架構中。 支援是內建 Windows 7 和眼前 Windows Presentation Foundation (WPF) 4。 (Microsoft 曲面圖電腦同時也以 WPF 周圍的但包含非常特殊的功能的自訂擴充功能)。

在本文中我喜歡著重於在 Silverlight 3 multi-touch 支援。 已支援有點上淺側邊,但它 ’s 肯定足夠,且非常適合探索 multi-touch 的基本概念。

如果您發佈 multi-touch 的 Silverlight 應用程式,以您的] 網站可以使用它嗎? 使用者將需要 multi-touch,監視的當然但將也必須執行受作業系統與支援 multi-touch 的瀏覽器 Silverlight 應用程式的需要。 現在,Windows 7 下執行的網際網路檔案總管 8 提供這項支援且可能是多個 OS 和瀏覽器會支援 multi-touch 在未來。

multi-touch Silverlight 3 的支援是由五個類別,一個委派,一個列舉型別和單一事件所組成。 沒有任何方法來決定是否 multi-touch 的裝置上執行 Silverlight 程式或,如果它是多少觸控點裝置支援。

想要回應 multi-touch 的 Silverlight 應用程式必須將處理常式附加至靜態 Touch.FrameReported 事件:

Touch.FrameReported += OnTouchFrameReported;

您可以附加 don’t 有 multi-touch 監視器的機器上這個事件處理常式,並沒有任何錯誤會發生。 FrameReported 事件就是只公用靜態觸控類別的成員。 這個處理常式看起來會像這樣:

void OnTouchFrameReported(
  object sender, TouchFrameEventArgs args) {
  ...
}

您可以在應用程式中安裝多個 Touch.FrameReported 事件處理常式,它們全部會報告所有觸控式事件在應用程式中的任何地方。

TouchFrameEventArgs 有一個名為我 haven’t 必須使用,場合的時間戳記的公用屬性和三個基本的公用方法:

  • TouchPoint GetPrimaryTouchPoint(UIElement relativeTo)
  • TouchPointCollection GetTouchPoints(UIElement relativeTo)
  • void SuspendMousePromotionUntilTouchUp()

引數為 GetPrimaryTouchPoint 或 GetTouchPoints 只用於報告 TouchPoint 物件中的位置資訊。 您可以使用 null 為此引數,位置資訊再也會相對於左上角的整個 Silverlight 應用程式。

multi-touch 支援多個手指接觸在螢幕,每個手指接觸螢幕 (最多至最大數目,目前通常是兩個) 觸控點。 主要的觸控式點指的是沒有其他手指會接觸螢幕和未按下滑鼠按鈕時,接觸到螢幕的手指。

碰觸到螢幕的手指。 ’s 主要觸控點。 與第一個手指靜止接觸螢幕,將放置在畫面上的第二個手指。 很明顯地該第二個手指不是主要的觸控式點。 但現在,與仍顯示在螢幕上第二個手指,提起第一個手指然後放在畫面上。 是一個主要接觸點嗎? 否,它 ’s 不。 主要的觸控式點發生於只在沒有其他手指會接觸螢幕時。

主要觸控點,對應到觸控點,將會提升至滑鼠。 真實 multi-touch 應用程式中您應該小心不要依賴主要觸控] 點因為使用者將通常會將不附加特定的意義到第一個觸控式。

僅供實際接觸螢幕的手指引發事件。 非常接近至螢幕,但 touching 手指沒有動態顯示偵測。

預設情況下,涉及主要觸控點的活動升級為各種滑鼠事件。 這可讓您現有的應用程式回應碰觸而不使用任何特定的編碼方式。 接觸螢幕變成移動手指時它還是接觸螢幕會變成一個 MouseMove 及提升將手指是一個 MouseLeftButtonUp 一個 MouseLeftButtonDown 事件。

伴隨著滑鼠訊息 [MouseEventArgs 物件包含了一個名為 StylusDevice 可協助您區分從手寫筆滑鼠事件並碰觸事件屬性。 它是我的經驗與 DeviceType 屬性值等於 TabletDeviceType.Mouse,當事件是來自於滑鼠和 TabletDeviceType.Touch 不論是否在螢幕觸及與手指或手寫筆的 PDC 膝上型電腦。

只有主要觸控點提升至滑鼠事件和 — 為名稱的第三個方法 TouchFrameEventArgs 建議 — 禁止該升級。 更多關於這很快就。

可能會引發事件的特定 Touch.FrameReported 根據一個觸控點或多個觸控點。 從 GetTouchPoints 方法傳回 TouchPointCollection 包含與特定事件相關聯的所有接觸點。 傳回從 GetPrimaryTouchPoint TouchPoint 永遠是主要的觸控式點。 如果沒有與特定事件相關聯的主要觸控點 GetPrimaryTouchPoint 會傳回 null。

即使從 GetPrimaryTouchPoint 傳回 TouchPoint 為非 Null,它不會相同的物件因為雖然如果引數傳遞至方法,所有屬性將都會相同,從 GetTouchPoints,傳回 TouchPoint 物件之一的是相同。

TouchPoint 類別會定義下列四個僅取得的屬性,所有備份由相依性屬性:

  • 型別 TouchAction 列舉型別與成員移動 Down 和 Up 動作。
  • 相對於項目類型點的位置做為引數傳遞,GetPrimaryTouchPoint 或 GetTouchPoints 方法 (或相對於應用程式引數的 Null 值的左上角)。
  • 型別大小的大小。 大小資訊不在 PDC 膝上型電腦上使用,所以我 didn’t 在所有使用這個屬性。
  • 型別 TouchDevice TouchDevice。

只有當 GetPrimaryTouchPoint 傳回非 Null 物件並動作屬性值等於 TouchAction.Down,您可以從事件處理常式呼叫 SuspendMousePromotionUntilTouchUp 方法。

TouchDevice 物件有兩個僅取得的屬性也由相依性屬性支援:

  • 型別 UIElement DirectlyOver — 下方手指最上層的項目。
  • 識別碼為型別 int。

DirectlyOver 就不一定要傳遞給 GetPrimaryTouchPoint 或 GetTouchPoints 項目的子系。 這個屬性可以是 null (如果手指是在 Silverlight 應用 (所定義的 Silverlight 外掛程式物件的尺寸),但是不可以在被擊中可測試的控制項所內含的區域內。 (使用 null 背景筆刷的面板不是擊中可測試。

[識別碼] 屬性是很重要的區別在多個手指之間。 當手指接觸到螢幕後面接著移動事件,分頁裝訂與向上事件後,就會永遠特定系列的特定手指相關聯的事件開始與一個動作的下。 這些事件將會與相同的識別碼。 (但 don’t 假設主要觸控點將會有識別碼值 0 或 1)。

最非一般 multi-touch 程式碼會讓使用一個字典的位置的 TouchDevice 的 [識別碼] 屬性是字典索引鍵的集合。 這是您如何跨事件將會儲存特定手指的資訊。

檢查事件

當瀏覽新的輸入的裝置,它 ’s 一定很有幫助他們 ’re 像寫入記錄在螢幕上的事件,所以您可以取得了解什麼的一些應用程式。 可下載的程式碼所附本文之間專案命名 MultiTouchEvents。 這個專案是由兩個-並存 TextBox 控制項顯示兩個手指 multi-touch 的事件所組成。 如果您有 multi-touch 監視器您可以在 charlespetzold.com/silverlight/MultiTouchEvents 執行這個程式。

XAML 檔案所組成的只是一個兩欄格線,包含兩個名為 txtbox1 和 txtbox2 的 TextBox 控制項。 程式碼檔案 的 圖 1 所示。

圖 1 Code MultiTouchEvents

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace MultiTouchEvents {
  public partial class MainPage : UserControl {
    Dictionary<int, TextBox> touchDict = 
      new Dictionary<int, TextBox>();

    public MainPage() {
      InitializeComponent();
      Touch.FrameReported += OnTouchFrameReported;
    }

    void OnTouchFrameReported(
      object sender, TouchFrameEventArgs args) {

      TouchPoint primaryTouchPoint = 
        args.GetPrimaryTouchPoint(null);

      // Inhibit mouse promotion
      if (primaryTouchPoint != null && 
        primaryTouchPoint.Action == TouchAction.Down)
        args.SuspendMousePromotionUntilTouchUp();

      TouchPointCollection touchPoints = 
        args.GetTouchPoints(null);

      foreach (TouchPoint touchPoint in touchPoints) {
        TextBox txtbox = null;
        int id = touchPoint.TouchDevice.Id;
        // Limit touch points to 2
        if (touchDict.Count == 2 && 
          !touchDict.ContainsKey(id)) continue;

        switch (touchPoint.Action) {
          case TouchAction.Down:
            txtbox = touchDict.ContainsValue(txtbox1) ? 
              txtbox2 : txtbox1;
            touchDict.Add(id, txtbox);
            break;

          case TouchAction.Move:
            txtbox = touchDict[id];
            break;
 
          case TouchAction.Up:
            txtbox = touchDict[id];
            touchDict.Remove(id);
            break;
        }

        txtbox.Text += String.Format("{0} {1} {2}\r\n", 
          touchPoint.TouchDevice.Id, touchPoint.Action, 
          touchPoint.Position);
        txtbox.Select(txtbox.Text.Length, 0);
      }
    }
  }
}

請注意字典定義在類別的頂端。 字典會追蹤的文字方塊是兩個觸控點 ID 相關聯。

藉由禁止所有滑鼠促銷開始 OnTouchFrameReported 處理常式。 ’s GetPrimaryTouchPoint,呼叫的唯一原因,並經常唯一的理由您會呼叫這個方法在真實程式中。

透過從 GetTouchPoints 傳回 TouchPointCollection TouchPoint 成員列舉 foreach 迴圈。 因為程式包含只有兩個文字方塊控制項,而且僅配備來處理觸控式的兩個點,它會忽略任何觸控點字典已經有兩個,而且該識別碼不該字典中。 (只是依您想要多-touch 感知 Silverlight 程式來處理多個手指,don’t 在您要它損毀遇到太多的手指 !)識別碼新增到下的事件字典,並從上往上事件字典中移除。

您請注意,有些時候 TextBox 控制項取得 bogged 向下有太多文字,而您必須選取所有文字,並刪除它 (Ctrl-A,Ctrl-X) 以取得一次執行順暢的程式。

從這個程式注意是擷取 multi-touch 輸入時,會在應用程式層級上。 比方說如果您按下應用程式上的手指,並再將它移應用程式關閉應用程式將繼續接收移動事件與最後向上事件,當您提起手指。 在實際上一旦應用程式取得輸入一些 multi-touch,禁止 multi-touch 輸入至其他應用程式,滑鼠游標消失。

multi-touch 輸入這個應用程式中心擷取允許 MultiTouchEvents 應用程式本身的非常確定。 例如上移,] 及 [下移事件程式只是假設該 ID 將是字典中。 在實際的應用程式您可能想多項目符號校對萬一奇怪的東西會發生,但是您永遠取得 Down 事件。

兩個手指操作

其中一個標準的 multi-touch 案例是相片藝廊,可讓您移動、 調整大小和旋轉手指的相片。 我決定試試類似 — 只要讓我自己原則的有點熟悉涉及 — 但較簡單也。 我版本的程式有只有單一項目以操作文字字串的字 「 觸控 」。  您可以在 charlespetzold.com/silverlight/TwoFingerManipulation 在我的網站上執行 TwoFingerManipulation 程式。

當您撰寫程式碼 multi-touch 的應用程式時您可能永遠禁止多-touch 感知的控制項的滑鼠升級。 但是,讓您的程式沒有 multi-touch 監視器可用,您也新增特定的滑鼠處理。

如果您有使用滑鼠或單一手指、 您仍然可以移動 TwoFingerManipulation 的程式內字串但您可以變更其位置 — 稱為轉譯的圖形作業。 與 multi-touch 螢幕上的兩個手指,可以也縮放物件並旋轉它。

當我以板及畫筆來找出我需要為此縮放比例] 和 [旋轉演算法 sat 向下時,它很快就成為明顯是沒有唯一的解決方案 !

假設一個手指會保持固定在點 ptRef。 (所有點都是相對於下方正在操作物件的顯示介面)。其他手指會從點 ptOld 移到 ptNew。 如 的 圖 2 所示,您可以使用這些三個點單獨來計算物件的水平和垂直縮放比例因素。


圖 2 的 轉換縮放係數兩 Finger 移動

比方說,水平縮放比例是在 ptOld.X 及從 ptRef.X,ptNew.X 的距離增加或:

scaleX = (ptNew.X – ptRef.X) / (ptOld.X – ptRef.X)

垂直縮放比例很類似。 為使本範例在 的 圖 2,水平縮放比例為 2,而垂直縮放比例為 ½。

這當然是它的程式碼更容易的方法。 還,程式似乎如果兩個手指旋轉物件也更自然函式。 的 圖 3 所示。


圖 3 的 旋轉和縮放比例轉換兩 Finger 移動

第一、 兩個向量角度 — 從 ptRef ptOld,及 ptRef ptNew — 計算。 (Math.Atan2 方法非常適合這項工作)。然後 ptOld 是相對於 ptRef 旋轉由這些角度的差異。 與 ptRef 和 ptNew 再用此旋轉的 ptOld 來計算縮放比例因素。 這些擴充性的因素很少是因為旋轉元件已被移除。

實際演算法 (C# 檔案中 ComputeMoveMatrix 方法中實作) 是相當簡單。 不過,程式也必須有一大堆轉換支援程式碼,以彌補了沒有公用 Value 屬性或矩陣乘法在 WPF 中的 Silverlight 轉換類別的缺點。

在實際程式同時手指可以移動在同一時間,並處理兩個手指之間互動是比一開始看起來更簡單。 每個移動手指被處理獨立使用做為參考點的 [其他手指。 儘管計算增加的複雜性,結果看起來更為自然,我認為有 ’s 簡單說明:在現實生活是很常見旋轉您的手指的物件,但非常不尋常縮放它們。

在現實世界裡就可能合理時由只有一個手指或滑鼠操作物件實作的因此通常會旋轉。 這示範替代 AltFingerManipulation 程式 (可執行在 charlespetzold.com/silverlight/AltFingerManipulation )。 對兩個手指程式 TwoFingerManipulation 相同。 為一個手指它會計算該物件相對於中心的旋轉,然後再在轉譯的從中央使用任何多餘的移動方式。

換行與多個事件事件

通常我喜歡使用 Microsoft thoughtfully 提供一個架構,而不是包裝在我自己的程式碼的類別。 但我必須記住某些 multi-touch 我認為將受益於更複雜的事件介面的應用程式。

我想先更模組化的系統。 我想要混合會處理他們自己觸控輸入與現有的 Silverlight 的自訂控制項只是讓 [觸控輸入的控制項轉換成滑鼠輸入。 我也想實作擷取。 雖然 Silverlight 應用程式本身會擷取 multi-touch 裝置,我想要單獨擷取特定觸控點的個別控制項。

我也想 Enter 和保留事件。 就某種意義來說這些事件是擷取典範相反。 若要了解差異],想像一下其中每個索引鍵是 PianoKey 控制項的執行個體的鋼琴螢幕小鍵盤]。 第一次,您可能會認為這些按鍵與滑鼠觸發按鈕一樣。 上向事件下滑鼠鋼琴鍵開啟附註,並啟動事件滑鼠在它關閉附註。

但是,’s 不是您想要鋼琴機碼。 您想執行手指向上和向下若要製作 glissando 效果鍵盤的能力。 機碼真的 shouldn’t 甚至擔心與 Down 和 Up 事件。 他們真的 ’re 只與輸入和保留事件有關。

WPF 4 和 Microsoft 介面已經先前已經傳閱觸控式事件,它們可能來自至 Silverlight 在未來。 但我見我目前的需求與我呼叫在 TouchDialDemos 方案中 Petzold.MultiTouch 程式庫專案中實施的 TouchManager 類別。 大部分的 TouchManager 是由靜態方法、 欄位及 Touch.FrameReported 事件,讓它來管理整個應用程式的觸控式事件的靜態處理常式所組成。

想要以 TouchManager 註冊建立執行個體的類別如下:

TouchManager touchManager = new TouchManager(element);

建構函式引數屬於類型 UIElement,而且通常它會是物件的建立項目:

TouchManager touchManager = new TouchManager(this);

註冊 TouchManager 類別表示興趣其中的 TouchDevice 的 [DirectlyOver] 屬性是傳遞至 TouchManager 建構函式項目的子系的所有 multi-touch 事件,且這些 multi-touch 事件應該不升級至滑鼠事件。 沒有任何方法來解除登錄項目。

建立新的執行個體的 TouchManager 之後, 類別可以安裝名為 TouchDown、 TouchMove、 TouchUp、 TouchEnter、 TouchLeave 及 LostTouchCapture 的事件處理常式:

touchManager.TouchEnter += OnTouchEnter;

所有處理常式被定義在 < TouchEventArgs > EventHandler 委派的根據:

void OnTouchEnter(
  object sender, TouchEventArgs args) {
  ...
}

TouchEventArgs 定義四個屬性:

  • 來源類型原本就是項目的 UIElement 傳遞至 TouchManager 建構函式。
  • 型別點的位置。 這個位置是相對於來源。
  • 型別 UIElement,DirectlyOver 只被複製從 TouchDevice 物件。
  • 型別 int 也只是複製從 TouchDevice 物件識別碼。

只有在處理時 TouchDown 事件會是允許呼叫擷取方法的觸控式點 ID 與該事件相關聯的類別:

touchManager.Capture(id);

所有進一步該識別碼的觸控輸入前往直到 TouchUp 事件或 ReleaseTouchCapture 明確呼叫與此 TouchManager 執行個體相關聯的項目。 不論是哪一種情況 TouchManager 就會引發 LostTouchCapture 事件。

事件通常是在順序:TouchEnter、 TouchDown、 TouchMove、 TouchUp、 TouchLeave 及 LostTouchCapture (如果適用的話)。 當然可以有多個 TouchMove 事件 TouchDown 和 TouchUp 之間。 時不會擷取觸控點可以有多個事件以順序 TouchLeave,TouchEnter 及 TouchMove 觸控點離開已註冊的一個項目,並進入另一個。

TouchDial 控制項

變更在使用者輸入模範通常需要您到問題舊假設適當設計的控制項和其他輸入的機制。 比方說一些 GUI 控制項是為 solidly 駐防壕溝作為捲軸和滑動軸。 您瀏覽大型文件或影像,使用這些控制項,但也很小的磁碟區控制媒體播放器上。

如我視為製作一個螢幕上音量控制會回應碰觸,我總會舊的方法是否真的正確。 在真實世界中滑桿有時當作音量控制,但通常限於專業的混合面板或圖形 equalizers 上。 在現實世界裡大部分的磁碟區控制項是撥。 撥號可能會為已啟用觸控式的磁碟區控制項更好的解決方案吗?

我 won’t 假裝我有明確的答案,但是我告訴您如何建置一個。

TouchDial 控制項包含在 Petzold.MultiTouch 媒體櫃 TouchDialDemos 方案中 (請參閱程式碼下載如需詳細資訊)。 TouchDial 衍生自 RangeBase,這樣它才會最小值]、 [最大值] 和 [值] 屬性的優點 — 包括強制型轉邏輯,以保持在最小和最大範圍內值 — 和 ValueChanged 事件。 但在 TouchDial,最小值]、 [最大值] 和 [值] 屬性所有角度的度數為遞增值的單位。

TouchDial 會回應滑鼠和碰觸,以及它來擷取觸控點使用 TouchManager 類別。 使用任一個滑鼠或觸控輸入,TouchDial 變更 Value 屬性移動事件期間會根據新的位置和滑鼠的前一位置或 finger 相對於中心點。 動作是相當類似於 的 圖 3,不同之處在於涉及不進行縮放。 移動事件使用 Math.Atan2 方法來將笛卡兒座標轉換為角度,然後在值中新增兩個角度的差異。

TouchDial 不包含預設的範本,因此沒有預設的視覺外觀。 使用 [TouchDial 時您的工作,就是提供一個範本,但它可以簡單,例如幾個項目。 很明顯地在此範本上的東西可能應該旋轉以配合 [值] 屬性中的變更。 為了方便起見,TouchDial 會提供一個僅取得 RotateTransform 其中 Angle 屬性值等於 [RangeBase] 的 [值] 屬性和屬性 CenterX 和 CenterY 屬性反映控制項的中央。

圖 4 顯示使用參照樣式及範本做為資源定義的兩個 TouchDial 控制項的 XAML 檔案。

圖 4 的 SimpleTouchDialTemplate 專案的 XAML 檔案

<UserControl x:Class="SimpleTouchDialTemplate.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:multitouch="clr-namespace:Petzold.MultiTouch;assembly=Petzold.MultiTouch">
  <UserControl.Resources>
    <Style x:Key="touchDialStyle" 
      TargetType="multitouch:TouchDial">
      <Setter Property="Maximum" Value="180" />
      <Setter Property="Minimum" Value="-180" />
      <Setter Property="Width" Value="200" />
      <Setter Property="Height" Value="200" />
      <Setter Property="HorizontalAlignment" Value="Center" />
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="multitouch:TouchDial">
            <Grid>
              <Ellipse Fill="{TemplateBinding Background}" />
              <Grid RenderTransform="{TemplateBinding RotateTransform}">
                <Rectangle Width="20" Margin="10"
                  Fill="{TemplateBinding Foreground}" />
              </Grid>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </UserControl.Resources>
    
  <Grid x:Name="LayoutRoot">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
        
    <multitouch:TouchDial Grid.Column="0"
      Background="Blue" Foreground="Pink"
      Style="{StaticResource touchDialStyle}" />
        
    <multitouch:TouchDial Grid.Column="1"
      Background="Red" Foreground="Aqua"
      Style="{StaticResource touchDialStyle}" />
  </Grid>
</UserControl>

請注意樣式將最大值屬性設定為 180 和-180 到最小,讓列會於向左或向右旋轉 180 度。 (怪異,程式未正常運作時我樣式定義中切換這些兩個屬性的順序)。撥號只包含內的橢圓形的矩形項目從一個列。 [列是在已繫結至 RotateTransform 屬性由 TouchDial 計算其 RenderTransform 的一個單一儲存格] 方格內。

SimpleTouchDialTemplate 程式會顯示在 的 圖 5 中執行。


圖 5 的 SimpleTouchDialTemplate 程式

您可以 (連同下面兩個的程式,我這裡討論) 嘗試它出在 charlespetzold.com/silverlight/TouchDialDemos

在圓圈內開啟列是有點怪怪,用滑鼠,感覺與一個手指更為自然。 請注意您可以開啟列在您按住滑鼠左鍵 (或將手指放在螢幕上) 任何地方在圓圈內。 同時開啟列,您可以移動滑鼠或手指離開因為兩者所擷取。

如果想限制使用者開啟列,除非直接放列按下滑鼠或手指您可以將 [橢圓] 的 [IsHitTestVisible] 屬性設為 False。

我第一個版本的 TouchDial 控制項 didn’t 包括 RotateTransform 屬性。 它由範本可能包含 Angle 屬性已給控制項的 Value 屬性 TemplateBinding 目標的其中一個明確 RotateTransform 寄給我更多的意義。 不過,在 Silverlight 3 繫結 don’t 處理屬性的類別不是從衍生 FrameworkElement,所以 RotateTransform] 的 [角度] 屬性 can’t 會繫結目標 (這固定在 Silverlight 4)。

旋轉恆參考以中心點,且該小事實 complicates TouchDial 控制項。 TouchDial 以兩種方法來使用一個中心點:若要計算 圖 3 所示的角度並也設定 [CenterX 和 CenterY 屬性 RotateTransform 它建立。 預設情況下,TouchDial 會計算兩個中心為半 [ActualWidth 和 ActualHeight 屬性,也就是控制項的中央,但是有很多情況下,不 ’s 相當您想要的地方。

例如 的 圖 4 在範本中, 假設您要將該矩形的 RenderTransform 屬性繫結至的 TouchDial RotateTransform 屬性。 因為 TouchDial 設定 RotateTransform CenterX 和 CenterY 屬性設為 100,但相對於本身矩形的中心是實際點 (10,90),它 won’t 正常運作。 若要讓您覆寫 TouchDial 會從控制項的大小計算這些預設值,控制項定義 RenderCenterX 和 RenderCenterY 屬性。 SimpleTouchDialTemplate 屬性中,您可以在樣式中設定這些屬性如下:

<Setter Property="RenderCenterX" Value="10" />
<Setter Property="RenderCenterY" Value="90" />

或者您可以設定為零,並設定以指出相對於本身中央矩形項目的 RenderTransformOrigin 這些屬性:

RenderTransformOrigin="0.5 0.5"

您也可能要在其中將點用來參考滑鼠或手指移動 isn’t 中央的控制項的情況下使用 TouchDial。 在這種情況下,您可以設定 InputCenterX 和 InputCenterY] 屬性,以覆寫預設值。

圖 6 顯示 OffCenterTouchDial 專案 XAML 檔案。

圖 6 的 OffCenterTouchDial XAML 檔案

<UserControl x:Class="OffCenterTouchDial.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:multitouch="clr-namespace:Petzold.MultiTouch;assembly=Petzold.MultiTouch">
  <Grid x:Name="LayoutRoot">
    <multitouch:TouchDial Width="300" Height="200" 
      HorizontalAlignment="Center" VerticalAlignment="Center"
      Minimum="-20" Maximum="20"
      InputCenterX="35" InputCenterY="100"
      RenderCenterX="15" RenderCenterY="15">
      <multitouch:TouchDial.Template>
        <ControlTemplate TargetType="multitouch:TouchDial">
          <Grid Background="Pink">
            <Rectangle Height="30" Width="260"
              RadiusX="15" RadiusY="15" Fill="Lime"
              RenderTransform="{TemplateBinding RotateTransform}" />
            <Ellipse Width="10" Height="10"
              Fill="Black" HorizontalAlignment="Left"
              Margin="30" />
          </Grid>
        </ControlTemplate>
      </multitouch:TouchDial.Template>
    </multitouch:TouchDial>
  </Grid>
</UserControl>

這個檔案包含位置屬性在控制項本身,設定且 [範本] 屬性設定為控制項範本,包含矩形與橢圓形的單一儲存格格線的單一 TouchDial 控制項。 橢圓形是,您可以旋轉向上及向下 20 度 的 圖 7 所示的矩形的小符號的樞紐分析點。


圖 7 的 OffCenterTouchDial 程式

[InputCenterX] 和 [InputCenterY 屬性是總是相對於整個控制項中,因此它們表示橢圓元素粉紅色的格線內的中心點位置。 [RenderCenterX] 和 [RenderCenterY 屬性一律是相對於 RotateTransform 屬性套用的控制項的一部分。

音量控制和字幅管道

兩個的前一個範例說明如何您可以讓視覺外觀 TouchDial 任一個範本中設定屬性明確地標記或,如果您需要共用依參考一個 ControlTemplate 的多個控制項之間的範本定義為資源。

您也可以從 TouchDial 衍生新的類別,並僅供設定範本使用 XAML 檔案]。 這是與 RidgedTouchDial Petzold.MultiTouch 媒體櫃中的情況。 除了它有特定的大小和視覺化的外觀 (這您稍後看到) RidgedTouchDial 都是 TouchDial 一樣的。

另外,也可以使用 TouchDial (或衍生的類別像 RidgedTouchDial) 在類別內衍生自使用者控制項。 這種方法的優點是您可以隱藏包括最小值、 最大值和數值的 RangeBase 所定義的所有屬性,並取代新的屬性。

這是與 VolumeControl 情況。 VolumeControl 衍生自 RidgedTouchDial 針對其視覺外觀,並定義一個名為磁碟區的新屬性。 相依性屬性所備份磁碟區屬性,並且對該屬性所做的任何變更將觸發 VolumeChanged 事件]。

對於 VolumeControl XAML 檔案只需參考 RidgedTouchDial 控制項,並將設定包括最小值、 最大值和數值的數個屬性:

<src:RidgedTouchDial 
  Name="touchDial"
  Background="{Binding Background}"
  Maximum="150"
  Minimum="-150"
  Value="-150"
  ValueChanged="OnTouchDialValueChanged" />

因此,[TouchDial 可以旋轉透過 300 度從最小位置到最大位置。 圖 8 顯示 [VolumeControl.xaml.cs。 控制項將撥號 300 度範圍轉譯為對數 decibel 比例 0 到 96。

圖 8 的 為 VolumeControl 的 C# 檔案

using System;
using System.Windows;
using System.Windows.Controls;

namespace Petzold.MultiTouch {
  public partial class VolumeControl : UserControl {
    public static readonly DependencyProperty VolumeProperty =
      DependencyProperty.Register("Volume",
      typeof(double),
      typeof(VolumeControl),
      new PropertyMetadata(0.0, OnVolumeChanged));

    public event DependencyPropertyChangedEventHandler VolumeChanged;

    public VolumeControl() {
      DataContext = this;
      InitializeComponent();
    }

    public double Volume {
      set { SetValue(VolumeProperty, value); }
      get { return (double)GetValue(VolumeProperty); }
    }

    void OnTouchDialValueChanged(object sender, 
      RoutedPropertyChangedEventArgs<double> args) {

      Volume = 96 * (args.NewValue + 150) / 300;
    }

    static void OnVolumeChanged(DependencyObject obj, 
      DependencyPropertyChangedEventArgs args) {

      (obj as VolumeControl).OnVolumeChanged(args);
    }

    protected virtual void OnVolumeChanged(
      DependencyPropertyChangedEventArgs args) {

      touchDial.Value = 300 * Volume / 96 - 150;

      if (VolumeChanged != null)
        VolumeChanged(this, args);
    }
  }
}

為什麼是 96? 嗯雖然 decibel 比例以十進位數字為基礎 — 只要 10 個乘法係數來增加訊號 amplitude,音量大小增加而呈線性成長 20 分貝 — 也是如此第三次方的 10 是大約 2 第 10 的乘冪。 這表示當 amplitude 增加一倍,音量大小增加 6 分貝。 因此,如果代表 amplitude 以 16 位元值 — 它是以 CD 與 PC 的音效情況 — 每位元,6 分貝或 96 分貝,取得的 16 位元的時間範圍。

PitchPipeControl 類別也是衍生自使用者控制項,並定義一個名為頻率的新屬性。 XAML 檔案包括 TouchDial 控制項,以及一大堆 TextBlocks 顯示的 octave 12 的筆記。 也會利用另一個屬性的 TouchDial PitchPipeControl 我 haven’t 討論還:如果您在 [角度] 中設定為非零值的 SnapIncrement,撥號影片不會平滑,不過會跳之間的增量。 因為 PitchPipeControl 可以設定的 octave 12 備忘稿,[SnapIncrement 會設為 30 度。

圖 9 顯示結合了 VolumeControl 及 PitchPipeControl PitchPipe 程式。 您可以在 charlespetzold.com/silverlight/TouchDialDemos 執行 PitchPipe。


圖 9 的 PitchPipe 程式

加分程式

本文稍早我提到名 PianoKey 為範例的內容中的控制項。 PianoKey 是實際的控制項,以及可以在鋼琴程式可以在 charlespetzold.com/silverlight/Piano 執行數個控制項的其中一個。 程式被為了以最大化您的瀏覽器顯示。 (或按 F11 以使 Internet Explorer 進入全螢幕模式,並取得更多的操作空間。非常小 rendition 的 圖 10 所示。 鍵盤分成重疊高音和低音部份。 紅點表示中間 C。


圖 10 的鋼琴程式

它是這個程式我撰寫 TouchManager,因為鋼琴程式以三個不同的方式來使用觸控式。 我已經討論藍色 VolumeControl 擷取觸控點 TouchDown 事件上的,並釋放 TouchUp 的擷取。 將鍵盤所組成的 PianoKey 控制項也使用 TouchManager,但這些控制項只接聽 TouchEnter 和 TouchLeave 事件。 您的確可以執行手指跨 glissando 效果的按鍵。 褐色如承受踏板運作的矩形是普通的 Silverlight 切換控制項。 這些不會特別觸控-啟用 ; 而是觸控點會轉換成滑鼠事件。

鋼琴程式會示範三種不同的方法可以使用 multi-touch。 我懷疑有許多,更多其他。

 

Charles Petzold   是要 MSDN Magazine longtime 造成這個現象編輯器。 他最新的活頁簿是 「 的 Annotated Turing:A 引導式穿透 Alan Turing ’s 歷史紙張 Computability 和 Turing 機器上的教學課程 」 (Wiley,2008)。 在他的網站 charlespetzold.com Petzold 部落格。

多虧來檢閱本文的下列的技術專家:   劉小龍詳盡Anson Tsao