建立多點觸控應用程式

一、簡介

在上一篇文章 建立多點觸控模擬環境中,我們可以透過多個滑鼠模擬多點觸控環境,接著在本文中,將介紹如何撰寫多點觸控 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) 以下為程式執行結果,透過兩顆滑鼠模擬多點觸控,左手與右手同時進行繪圖動作。

【範例原始碼下載】

【範例可執行檔下載】