WPF進階技巧和實戰09-事件(2-多點觸控)

蝸牛的希望發表於2021-11-11

多點觸控輸入

多點觸控輸入和傳統的基於比的輸入的區別是多點觸控識別手勢,使用者可以移動多根手指以執行常見的操作,放大,旋轉,拖動等。

多點觸控的輸入層次

WPF允許使用鍵盤和滑鼠的高層次輸入(例如單擊和文字改變)和低層次輸入(滑鼠事件和按鍵事件)。多點觸控輸入同樣應用了這種多層次的輸入方式,WPF提供了3個獨立的層次:

  • 原始觸控(raw touch):這是最低階的支援,可訪問使用者執行的每個觸控。缺點是由應用程式負責將單獨的觸控訊息組合在一起,並對他們進行解釋。如果不準備識別標準觸控手勢,反而希望建立以獨特方式響應多點觸控輸入的應用程式,使用原始觸控是合理的。一個例子就是繪圖程式,允許使用者同時使用多根手指在觸控式螢幕上繪圖。
  • 操作(manipulation):這是一個簡便的抽象層,該層將原始的多點觸控輸入轉換成更有意義的手勢,與WPF控制元件將一系列MouseDown和MouseUp事件解釋為更高階的MouseDoubleClick事件相似。WPF支援的通用手勢包括移動Pan,縮放Zoom,旋轉Rotate,輕按Tap
  • 內建的元素支援:有些元素已經對多點觸控事件提供了內建的支援,從而不需要在編寫程式碼。例如,可滾動的控制元件支援觸控移動,ListBox、ListView、DataGrid、TextBox、ScrollViewer

原始觸控

觸控事件被內建到了低階的UIElement以及ContentElemnet類。所有元素的原始觸控事件:

名稱 路由型別 說明
PreviewTouchDown 隧道 當使用者觸控元素時發生
TouchDown 冒泡 當使用者觸控元素時發生
PreviewTouchMove 隧道 當使用者移動放到觸控式螢幕上的手指時發生
TouchMove 冒泡 當使用者移動放到觸控式螢幕上的手指時發生
PreviewTouchUp 隧道 當使用者移開手指,結束觸控時發生
TouchUp 冒泡 當使用者移開手指,結束觸控時發生
TouchEnter 當觸點從元素外進入元素內時發生
TouchLeave 當觸點離開元素時發生

所有這些事件都提供了一個TouchEventArgs物件,該物件提供了兩個重要成員。第一個是GetTouchPoint()方法,該方法返回觸控事件發生位置的螢幕座標(還有觸點的大小等)。第二個是TouchDevice屬性,該屬性返回一個TouchDevice物件。這裡的技巧是將每個觸點視為單獨裝置。因此,如果使用者在不同的位置按下兩根手指(同時按下或者先按下一個再按下一個),WPF將他們作為兩個觸控裝置,併為每個觸控裝置指定唯一的ID,當使用者移動這些手指,並且觸控事件發生時,程式碼可以通過TouchDevice.ID屬性區分兩個觸點。

操作

WPF為手勢提供了更高階的支援,稱為觸控操作。通過將元素的IsManipulationEnabled屬性設定為true,使元素接受觸控操作。然後可響應4個操作事件:

ManipulationStarting、ManipulationStarted、ManipulationDelta、ManipulationComplted。

<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>

public partial class Manipulations : Window
    {
        public Manipulations()
        {
            InitializeComponent();
        }

        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;
        }

        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;

        }
    }

上面每個影像都包含一個MatrixTransform物件,該物件為程式碼應用移動、旋轉、縮放等操作的組合提供了一個簡易方式。當操作發生時,將使用程式碼改變他們。
當觸控一幅圖時,將觸發ManipulationStarting事件。這時,設定操作容器(後面將獲得所有操作座標的參考點)。當操作發生時,觸發ManipulationDelta事件,例如使用者開始旋轉一幅影像,將不斷觸發這個事件,直到使用者旋轉停止或者抬起手指為止。

通過使用ManipulationDelta物件將手勢的當前狀態記錄下來,該物件是通過ManipulationDeltaEventArgs.DeltaManipulation屬性提供的。本質上,ManipulationDelta物件記錄了應該應用到物件的縮放、旋轉、移動的量,分別有3個簡單的屬性提供,Scale、Rotation、Translation,通過這3個資料來調整使用者介面的元素。

慣性

本質上,通過慣性可以更逼真的更流暢的操作元素。如上例中,當手指從觸控式螢幕抬起時影像會立即停止一定。但如果啟用了慣性特性,那麼影像會繼續移動非常短的一段時間,正常地減速。將元素拖動進他們不能穿過的邊界時,慣性還會使元素彈回來。

新增慣性特性,只需處理ManipulationInertiaStarting事件。該事件從一幅影像開始並冒泡到Canvas皮膚。當使用者結束手勢並抬起手指釋放元素時,觸發該事件。可使用ManipulationInertiaStartingEventArgs物件確定當前速度,並設定希望的減速度。

相關文章