建立多點觸控應用程式
一、簡介
在上一篇文章 建立多點觸控模擬環境中,我們可以透過多個滑鼠模擬多點觸控環境,接著在本文中,將介紹如何撰寫多點觸控 WPF 應用程式,而程式是在 Visual Studio 2008 中進行開發,功能為左手畫圓、右手畫方,也就是多個觸控點,各自進行繪圖動作。
二、方法
2.1 使用 Windows 7 Multitouch .NET Interop Sample Library
Windows 7 Multitouch .NET Interop Sample Library 提供 WPF 與 WinForms 3.5 SP1 進行 Multi-Touch 程式開發所需的 function,我們可以下載與使用它。首先連結至 Windows Touch: Developer Resources https://code.msdn.microsoft.com/WindowsTouch,下載 Windows 7 Multitouch .NET Interop Sample Library。
出現授權頁面,閱讀並同意內容後,點選按鈕 I Agree 進行下載。
將下載後的檔案進行解壓縮,解壓縮後內容如下圖所示
使用 Visual Studio 2008 開啟專案 Windows7.sln,裡面包含 Library 與 Demo 範例,其中 Demo 包含透過筆勢進行物件的旋轉、平移、縮放,以及處理原始觸控訊息進行繪圖等範例,有興趣的開發者可以將這些範例編譯並且執行看看。
接著新增 WPF 專案,並將前述的 Windows 7 Multitouch .NET Interop Sample Library (Windows7.Multitouch、Windows7.Multitouch.WPF) 加入,並將其編譯與加入參考中。
2.2 Windows 觸控訊息
在此章節中,透過程式撰寫,說明如何處理 Windows 觸控訊息。首先請大家回想一下,在處理滑鼠訊息時,可透過 Mouse Down、Move、Up等事件來處理,在多點觸控程式中,可以透過偵測觸控點的 Down、Move、Up事件來取得觸控點訊息,而不同的是,我們必須想辦法區分不同的觸控點所處發的訊息,而此問題可透過 StylusDevice ID 觸控點識別項來解決,可透過它來區別不同的觸控點;以下示範建立觸控事件與取得觸控點相關訊息。
(1) 程式初始化時,檢查硬體多點觸控支援裝置是否準備好。
// 檢查硬體多點觸控支援裝置是否準備好
if (!Windows7.Multitouch.TouchHandler.DigitizerCapabilities.IsMultiTouchReady)
{
MessageBox.Show("硬體多點觸控支援裝置不能使用");
Environment.Exit(1);
}
(2) 加入觸控時對應的事件。
// 加入觸控時對應的事件
Loaded += (s, e) => { Windows7.Multitouch.WPF.Factory.EnableStylusEvents(this); };
StylusDown += OnTouchDownHandler; // Touch Down
StylusMove += OnTouchMoveHandler; // Touch Move
StylusUp += OnTouchUpHandler; // Touch Up
// Touch down 事件處理
private void OnTouchDownHandler(object sender, StylusEventArgs e)
{
}
// Touch move 事件處理
private void OnTouchMoveHandler(object sender, StylusEventArgs e)
{
}
// Touch up 事件處理
private void OnTouchUpHandler(object sender, StylusEventArgs e)
{
}
(3) 在表單中加入控制項 Canvas(Name: _ canvas) 與 ListBox(Name: listBox1)。
(4) 在 Touch move 事件中,將 StylusDevice ID 與觸控點座標值顯示於 ListBox 中,並且在 Touch down 時做清除 ListBox。
// Touch down 事件處理
private void OnTouchDownHandler(object sender, StylusEventArgs e)
{
this.listBox1.Items.Clear();
}
// Touch move 事件處理
private void OnTouchMoveHandler(object sender, StylusEventArgs e)
{
this.listBox1.Items.Insert(0, "ID: " + e.StylusDevice.Id.ToString() + ", Point: " + e.GetPosition(_canvas));
}
(5) 程式執行後,如下圖所示,當我們在表單上觸控且移動時,在 ListBox 中會顯示 StylusDevice ID 與觸控點在 Canvas 的座標值;當有兩個觸控點作移動時,可得知 ID 有兩組,分別代表這兩個觸控點,利用這些資訊,我們就可以讓不同的觸控點各自進行繪圖。
2.3 左手畫圓、右手畫方
在前一個章節中,讓大家了解多點觸控程式,其實跟單點滑鼠程式是差不多的,只是在多點觸控中,需要區別各個觸控點,這部分就仰賴 StylusDevice ID 的幫務。接著,我們以之前的程式為基礎,撰寫多個觸控點各自繪圖的程式,在程式撰寫上,我們先建立 Stroke 類別,幫助我們在紀錄觸控點的 ID、Color,以及進行繪製的工作,並且在 Touch down、move、up 事件中使用它。
(1) 建立 Stroke Class
Stroke Class 是參考 Windows 7 Multitouch .NET Interop Sample Library 內的 demo 程式所建立的,程式碼如下所示。Stroke Class 包含兩個屬性,Color (設定 Storke 的顏色)、Id (透過 StylusDevice ID 辨識不同的觸控點)
// Stroke
// 功能 : 用來紀錄手指 Touch down 與 Touch up 期間的 Stroke
// 屬性 :
// Color : 設定 Stroke 的顏色
// Id : 透過 StylusDevice ID 辨識不同的觸控點
public class Stroke
{
private PathFigure _pathFigure;
private bool _isOnCanvas;
private Path _path = new Path();
private Color _color = Colors.Black;
private const float _penWidth = 3.0f; // 繪製 Stroke 的 Pen 寬度
/// <summary>
/// 凍結 Stroke 物件,用以提升效能
/// </summary>
public void Freeze()
{
if (_pathFigure != null)
_pathFigure.Freeze();
}
/// <summary>
/// 判斷 PathFigure 物件是否凍結且無法修改
/// </summary>
public bool IsFrozen
{
get
{
return _pathFigure.IsFrozen;
}
}
/// <summary>
/// 加入具有完整屬性的 Stroke 進行繪製
/// </summary>
/// <param name="canvas">Canvas</param>
public void AddToCanvas(Canvas canvas)
{
if (_isOnCanvas)
return;
_isOnCanvas = true;
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures = new PathFigureCollection() { _pathFigure };
_path.StrokeThickness = _penWidth;
_path.Data = pathGeometry;
canvas.Children.Add(_path);
}
/// <summary>
/// Strike 物件的 Color 屬性
/// </summary>
public Color Color
{
get
{
return _color;
}
set
{
_color = value;
_path.Stroke = new SolidColorBrush(_color);
}
}
/// <summary>
/// Stroke 物件的 ID 屬性
/// </summary>
public int Id { get; set; }
/// <summary>
/// 將 Point 加入 Stroke
/// </summary>
/// <param name="pt"> Point</param>
public void Add(Point pt)
{
if (_pathFigure != null && IsFrozen)
throw new InvalidOperationException(" PathFigure 物件是否凍結且無法修改");
if (_pathFigure == null)
{
_pathFigure = new PathFigure();
_pathFigure.IsClosed = false;
_pathFigure.StartPoint = pt;
_pathFigure.Segments = new PathSegmentCollection();
}
else
{
_pathFigure.Segments.Add(new LineSegment(pt, true));
}
}
(2) 宣告 Dictionary 類別物件 _activeStrokes,用來存放作用中的 Stroke 集合
// 宣告 Dictionary 類別物件 _activeStrokes,存放作用中的 Stroke 集合
private readonly Dictionary<int, Stroke> _activeStrokes = new Dictionary<int, Stroke>();
(3) 在 Touch down 事件中,先透過 StylusDevice ID,判斷是否已經存在於作用中的 Storke集合,假如存在的話,將其作凍結與移除;接著重新產生 Stroke,將 Color 與 Id 屬性作設定,並加入作用中的 Stroke 集合中。
// Touch down 事件處理
private void OnTouchDownHandler(object sender, StylusEventArgs e)
{
// 透過 StylusDevice ID 判斷是存在於作用中的 Stroke 集合,並將此 Stroke 作凍結與移除
Stroke stroke;
if (_activeStrokes.TryGetValue(e.StylusDevice.Id, out stroke))
{
FinishStroke(stroke);
return;
}
// 產生新的 Stroke
Stroke newStroke = new Stroke();
// 亂數產生 R, G, B 值
Random rand = new Random();
byte[] colorBytes = new byte[3];
rand.NextBytes(colorBytes);
// 新的 Stroke.Color 設定 Color 與 ID
newStroke.Color = Color.FromRgb(colorBytes[0], colorBytes[1], colorBytes[2]);
newStroke.Id = e.StylusDevice.Id;
// 將新的 Stroke 加入 _activeStrokes 作用中的 Stroke 集合
_activeStrokes[newStroke.Id] = newStroke;
}
(4) 在 Touch move 事件中,將移動中的觸控點設定座標,並且進行繪製動作。
// Touch move 事件處理
private void OnTouchMoveHandler(object sender, StylusEventArgs e)
{
//透過 StylusDevice ID 判斷是存在於作用中的 Stroke 集合
Stroke stroke;
if (_activeStrokes.TryGetValue(e.StylusDevice.Id, out stroke))
{
// 將座標加入 Stroke
stroke.Add(e.GetPosition(_canvas));
// 將具有座標的 Stroke 進行繪製
stroke.AddToCanvas(_canvas);
}
}
(5) 在 Touch up 事件中,透過 StylusDevice ID,判斷是否已經存在於作用中的 Storke 集合,假如存在的話,將其作凍結與移除。
// Touch up 事件處理
private void OnTouchUpHandler(object sender, StylusEventArgs e)
{
// 透過 StylusDevice ID 取得繪製中的 Stroke,並將此 Stroke 作釋放與移除
Stroke stroke;
if (_activeStrokes.TryGetValue(e.StylusDevice.Id, out stroke))
{
FinishStroke(stroke);
}
}
(6) 此外,由於在 Touch Up、Down 需要做 Stroke 的釋放與移除,用以增加效能,因此撰寫以下這段函式。
/// <summary>
/// 凍結與移除 Stroke
/// </summary>
/// <param name="stroke">Stroke</param>
private void FinishStroke(Stroke stroke)
{
// 凍結 Stroke
stroke.Freeze();
// 移除 Stroke 於繪製中的 Stroke
_activeStrokes.Remove(stroke.Id);
}
(7) 以下為程式執行結果,透過兩顆滑鼠模擬多點觸控,左手與右手同時進行繪圖動作。
【範例原始碼下載】
【範例可執行檔下載】