多點觸控(multi-touch)是經過觸摸屏幕與應用程序進行交互的一種方式。多點觸控輸入和更傳統的基於筆(pen-based)的輸入的區別是多點觸控識別手勢(gesture)——用戶可移動多根手指以執行常見操做的特殊方式。例如,在觸摸屏上放置兩根手指並同時移動他們,這一般意味着「放大",而以一根手指爲支點轉動另外一根手指意味着"旋轉"。而且由於用戶直接在應用程序窗口中進行這些手勢,因此每一個手勢天然會被鏈接到某個特定的對象。例如,簡單的具備多點觸控功能的應用程序,可能會在虛擬桌面上顯示多幅圖片,而且容許用戶拖動、縮放以及旋轉每幅圖片,進而建立新的排列方式。canvas
在智能手機和平板電腦上,多點觸控屏幕幾乎無處不在。但在普通計算機上,多點觸控屏幕較少見。儘管硬件製造商已經產生了觸摸屏筆記本電腦和LCD顯示器,但傳統的筆記本電腦和顯示器仍佔據主導地位。app
這對於但願實驗多點觸控應用程序的開發人員是一個挑戰。到目前位置,最好的方式是投資購買基本的多點觸控筆記本。然而,經過多作一點工做,可以使用仿真器模擬多點觸控輸入。基本作飯是爲計算機鏈接多個鼠標並安裝來自Multi-Touch Visita開源項目(對於Windows 7該項目也能工做)的驅動程序。具體安裝請自覺網上搜着安裝步驟。koa
1、多點觸控的輸入層次ui
正如前兩章所瞭解的,WPF容許使用鍵盤和鼠標的高層次輸入(例如單擊和文本改變)和低層次輸入(鼠標事件以及按鍵事件)。這很重要,由於有些應用程序須要加以更精細的控制。多點觸控輸入一樣應用了這種多層次的輸入方式,而且對於多點觸控支持,WPF提供了三個獨立的層次:spa
2、原始觸控code
與基本的鼠標和鍵盤事件同樣,觸控事件被內置到低級的UIElement以及ContentElement類。下表列出了全部觸控事件。orm
表 全部元素的原始觸控事件xml
名稱 | 路由類型 | 說明 |
PreviewTouchDown | 隧道 | 當用戶觸摸元素時發生 |
TouchDown | 冒泡 | 當用戶觸摸元素時發生 |
PreviewTouchMove | 隧道 | 當用戶移動放到觸摸屏上的手指時發生 |
TouchMove | 冒泡 | 當用戶移動放到觸摸屏上的手指時發生 |
PreviewTouchUp | 隧道 | 當用戶移開手指,結束觸摸時發生 |
TouchUp | 冒泡 | 當用戶移開手指,結束觸摸時發生 |
TouchEnter | 無 | 當出點從元素外進入元素內時發生 |
TouchLeave | 無 | 當出點離開元素時發生 |
全部這些事件都提供了一個TouchEventArgs對象,該對象提供了兩個重要成員。第一個是GetTouchPoint()方法,該方法返回觸控事件發生位置的屏幕座標(還有一些不怎麼經常使用的數據,例如觸點的大小)。第二個是TouchDevice屬性,該屬性返回一個TouchDevice對象。這裏的技巧是將每一個出點都視爲單獨設備。所以,若是用戶在不一樣的位置按下兩根手指(同時按下或者先按下一根再按下另外一根),WPF將它們做爲兩個觸控設備,併爲兩個觸控設備指定惟一的ID。當用戶移動這些手指,而且觸控事件發生時,代碼能夠經過ToucheDevice.Id屬性區分兩個觸點。對象
下面是一個簡單的示例:blog
爲了建立這個示例,須要處理TouchDown、TouchMove以及TouchUp事件:
<Window x:Class="Multitouch.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Canvas x:Name="canvas" Background="LightBlue" TouchDown="Canvas_TouchDown" TouchUp="canvas_TouchUp" TouchMove="canvas_TouchMove"> </Canvas> </Window>
爲了跟蹤全部觸點,須要做爲窗口成員變量存儲一個集合。最簡潔的方法是存儲UIElement對象的集合(爲每一個激活的橢圓存儲一個UIElement對象),是喲該觸控設備的ID(該ID是整數)編寫索引:
private Dictionary<int, UIElement> movingEllipses = new Dictionary<int, UIElement>();
當用戶按下一根手指時,代碼建立並配置一個新的橢圓元素(該元素看起來像個小圓)。使用觸點在恰當的座標放置橢圓,並將橢圓元素添加到集合中(根據觸控設備的ID編寫索引),而後再Canvas面板上顯示該橢圓元素:
private void Canvas_TouchDown(object sender, TouchEventArgs e) { //Create an ellipse to draw at the new contact point Ellipse ellipse = new Ellipse(); ellipse.Width = 30; ellipse.Height = 30; ellipse.Stroke = Brushes.White; ellipse.Fill = Brushes.Green; //Position the ellipse at the contact point TouchPoint touchPoint = e.GetTouchPoint(canvas); Canvas.SetTop(ellipse, touchPoint.Bounds.Top); Canvas.SetLeft(ellipse, touchPoint.Bounds.Left); //Store the ellipse in the active collection movingEllipses[e.TouchDevice.Id] = ellipse; //Add the ellipse to the Canvas canvas.Children.Add(ellipse); }
當用戶移動按下的手指時,將觸發TouchMove事件。此時,可以使用觸控設備的ID肯定哪一個點正在移動。代碼須要作的所有工做就是查找對相應的橢圓並更新其座標:
private void canvas_TouchMove(object sender, TouchEventArgs e) { //Get the ellipse that corresponds to the current contact point UIElement element = movingEllipses[e.TouchDevice.Id]; //Move it to the new contact point TouchPoint touchPoint = e.GetTouchPoint(canvas); Canvas.SetTop(element, touchPoint.Bounds.Top); Canvas.SetLeft(element, touchPoint.Bounds.Left); }
最後,當用戶擡起手指時,從跟蹤集合中移除橢圓。做爲一種選擇,您可能也但願從Canvas面板中移除橢圓:
private void canvas_TouchUp(object sender, TouchEventArgs e) { //Remove the ellipse from the Canvas UIElement element = movingEllipses[e.TouchDevice.Id]; canvas.Children.Remove(element); //Remove the ellipse from the tracking collection movingEllipses.Remove(e.TouchDevice.Id); }
注意:
UIElement還添加了CaptureTouch()和ReleaseTouchCapture()方法,這兩個方法與CaptureMouse()和ReleaseMouseCapture()方法相似。當一個元素捕獲觸控輸入後,該元素會接受來自被捕獲的觸控設備的全部觸控事件,即便觸控事件是在窗口的其餘地方發生的也是如此。但由於可能有多個觸控設備,因此多個元素可能同時捕獲觸控輸入,只要每個捕獲來自不一樣設備的輸入便可。
3、操做
對於那些以簡明直接的方式使用觸控事件的應用程序,例如上面介紹的拖動橢圓示例或畫圖程序,原始觸控是很是好的。可是,若是但願支持標準的觸控手勢,原始觸控不會簡化該工做。例如,爲了支持旋轉,須要探測在同一個元素上的兩個觸點,跟蹤這兩個觸點的移動狀況,並使用一些運算肯定一個觸點繞另外一個觸點的轉動狀況。甚至,伺候還須要添加實際應用相應旋轉效果的代碼。
幸運的是,WPF未將這些工做徹底留給你。WPF爲手勢提供了更高級別的支持,稱爲觸控操做(manipulation)。經過將元素的IsManipulationEnabled屬性設置爲True,將元素配置爲接受觸控操做。而後可響應4個操做時間:ManipulationStaring、ManipulationStared、ManipulationDelta以及ManipulationCompleted。
建立一個實例,該例使用基本的安排在Canvas面板上顯示三幅圖像。此後用戶可以使用移動、旋轉以及縮放手勢來移動、轉動、縮小或發達圖像。
建立這個示例的第一步是定義Canvas面板並放置三個Image元素。爲簡化實現,當ManipulationStarting和ManipulationDelta事件從適當的Image元素內部向上冒泡後,在Canvas面板中處理這兩個事件:
<Window x:Class="Multitouch.Manipulations" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Manipulations" Height="349" Width="607"> <Grid> <Canvas x:Name="canvas" ManipulationStarting="image_ManipulationStarting" ManipulationDelta="image_ManipulationDelta"> <Image Canvas.Top="10" Canvas.Left="10" Width="200" IsManipulationEnabled="True" Source="koala.jpg"> <Image.RenderTransform> <MatrixTransform></MatrixTransform> </Image.RenderTransform> </Image> <Image Canvas.Top="30" Canvas.Left="350" Width="200" IsManipulationEnabled="True" Source="penguins.jpg"> <Image.RenderTransform> <MatrixTransform></MatrixTransform> </Image.RenderTransform> </Image> <Image Canvas.Top="100" Canvas.Left="200" Width="200" IsManipulationEnabled="True" Source="tulips.jpg"> <Image.RenderTransform> <MatrixTransform></MatrixTransform> </Image.RenderTransform> </Image> </Canvas> </Grid> </Window>
上面的表中有一個新的細節。每一個圖像包含一個MatrixTransform對象,該對象爲代碼應用移動、旋轉以及縮放操做的組合提供了一種簡易方式。當前,MatrixTransform對象未執行任何操做,但當操做事件發生時,將使用代碼改變。
當用戶觸摸一幅圖像時,將觸發ManipulationStarting事件。這是,須要設置操做容器,它是在後面將得到的全部操做座標的參考點。在該例中,包含圖像的Canvas面板是天然自選。還可根據須要選擇容許的操做類型。若是不選擇操做類型,WPF將監視它識別的全部手勢:移動、縮放以及旋轉。
private void image_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { // Set the container (used for coordinates.) e.ManipulationContainer = canvas; // Choose what manipulations to allow. e.Mode = ManipulationModes.All; }
當發生操做是(但操做未必結束),觸發ManipulationDelta事件。例如,若是用戶開始選擇一幅圖像,將不斷觸發ManipulationDelta事件,直到用戶選擇結束而且用戶擡起按下的手指爲止。
經過使用ManipulationDelta對象將手勢的當前狀態記錄下來,該對象是經過ManipulationDeltaEventArgs.DeltaManipulation屬性提供的。本質上,ManipulationDelta對象記錄了應當應用到對象的縮放、旋轉以及移動的量,這些信息時經過三個簡單的屬性提供的:Scale、Rotation以及Translation。使用這一信息的技巧是在用戶界面中調整元素。
理論上,可經過改變元素的大小和位置來處理縮放和移動細節。但這仍不能應用旋轉(並且代碼有些凌亂)。更好的方法是使用變換——經過變換對象可採用數學方法改變任何WPF元素的外觀。基本思路是獲取由ManipulationDelta對象提供的信息,並使用這些信息配置MatrixTransform。儘管這聽起來很複雜,但須要使用的代碼在使用該特性的每一個應用程序中本質上時相同的。看起來以下所示:
private void image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { // Get the image that's being manipulated. FrameworkElement element = (FrameworkElement)e.Source; // Use the matrix to manipulate the element. Matrix matrix = ((MatrixTransform)element.RenderTransform).Matrix; var deltaManipulation = e.DeltaManipulation; // Find the old center, and apply the old manipulations. Point center = new Point(element.ActualWidth / 2, element.ActualHeight / 2); center = matrix.Transform(center); // Apply zoom manipulations. matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, center.X, center.Y); // Apply rotation manipulations. matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y); // Apply panning. matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y); // Set the final matrix. ((MatrixTransform)element.RenderTransform).Matrix = matrix; }
4、慣性
WPF還有一層構建在基本操做支持之上的特性,稱爲慣性(intertia)。本質上,經過慣性能夠更逼真、更流暢地操做元素。
如今,若是用戶用移動手勢拖動上例中的一幅圖像,當手指從觸摸屏上擡起時圖像會當即中止移動。但若是啓用了慣性特性,那麼圖像會繼續移動很是短的一段時間,正常地減速。該特性爲操做提供了勢頭的效果和感受。當將元素拖動進他們不能穿過的邊界時,慣性還會使元素被彈回,從而使他們的行爲像是真實的物理對象。
爲給上一個示例添加慣性特性,只須要處理ManipulationInertiaStarting事件。與其餘操做事件同樣,該事件從一幅圖像開始並冒泡至Canvas面板。當用戶結束手勢並擡起手指釋放元素時,觸發ManipulationInertiaStarting事件。這是,可以使用ManipulationInertiaStartingEventsArgs對象肯定當期速度——當操做結束時元素的移動速度——並設置但願的減速度。下面的示例爲移動、縮放以及旋轉手勢添加了慣性:
private void canvas_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e) { //If the object is moving,decrease its speed by //10 inches per second every second //deceleration=10 inches * 96 units per inch /(1000 milliseconds)^2 e.TranslationBehavior = new InertiaTranslationBehavior(); e.TranslationBehavior.InitialVelocity = e.InitialVelocities.LinearVelocity; e.TranslationBehavior.DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0); //Decrease the speed of zooming by 0.1 inches per second every second. //deceleration=0.1 inches * 96 units per inch/(1000 milliseconds)^2 e.ExpansionBehavior = new InertiaExpansionBehavior(); e.ExpansionBehavior.InitialVelocity = e.InitialVelocities.ExpansionVelocity; e.ExpansionBehavior.DesiredDeceleration = 0.1 * 96 / (1000.0 * 1000.0); //Decrease the rotation rate by 2 rotations per second every second. //deceleration=2*36 degress /(1000 milliseconds)^2 e.RotationBehavior = new InertiaRotationBehavior(); e.RotationBehavior.InitialVelocity = e.InitialVelocities.AngularVelocity; e.RotationBehavior.DesiredDeceleration=720/(1000.0*1000.0) }
爲使元素從障礙物天然地被彈回,須要在ManipulationDelta事件中檢查是否將元素拖到了錯誤的位置。若是穿過了一條邊界,那麼由你負責經過調用ManipulationDeltaEventArgs.ReportBoundaryFeedback()方法進行報告。