WPF4 ベータ2 における マニピュレーション イベント

.NET Framework 4 RCがリリースされましたが、ベータ2ベースにおける WPF のマルチタッチ対応の1つである、マニピューレーションを取り上げます。マニピュレーションを具体的に説明する前に、Windows 7におけるマルチタッチのWindow メッセージをを説明します。Windowsメッセージには、以下の2つが存在します。

  • WM_Touch:タッチ、移動、タッチアップという低レベルのメッセージ
  • WM_Gesture:タッチした動作によってどのような操作をしたいかを解釈するメッセージ

Windows 7 の標準的なマルチタッチメッセージは、WM_Gestureになります。ジェスチャの基本的な操作は、パン(移動)、ズーム(拡大、縮小)、ローテート(回転)になります。
Gesture

このジェスチャ操作が、Windows 7 製品版から1本の指でも可能なようになっています。このジェスチャメッセージに対応して、操作しようとする対象(オブジェクト)を簡単に操作するためにManipulationProcessorとInertiaProcessorがWin32APIでは提供されています。この機能に対応するのが、WPF 4 の Mnipulation イベントとなります。このイベントを記述したXAMLを以下に示します。

 <Window x:Class="Manipulation.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        Title="マニピュレーション" Height="500" Width="600"
    <Canvas x:Name="canvas"
            ManipulationStarting="canvas_ManipulationStarting"
            ManipulationDelta="canvas_ManipulationDelta"
            >
        <Image IsManipulationEnabled="True" x:Name="image1" 
                  Width="200" Source="Guitar.jpg">
            <Image.RenderTransform>
                <MatrixTransform />
            </Image.RenderTransform>
        </Image>
    </Canvas>
</Window>

Canvasで記述している ManipulationStarttingイベントとManipulationDeltaイベントがManipulationを行うために必要なイベントハンドラになります。また、Imageオブジェクトに記述している IsManipulationEnabled="True" という設定が、どのオブジェクトをManipulationイベントで操作するかということを表します。したがって、IsManipulationEnabledは、Manipulationで操作したいオブジェクトが複数あれば、対象毎に設定します(よくある、複数の写真を操作するのであれば写真のイメージ毎に設定します)。
WPF4でManipulation操作をするためには、Manipulationコンテナという考え方を理解しておく必要があります。Manipulationコンテナとイベントの関係を以下に示します。
Manipulation Container 

Manipulation コンテナとは、Manipulation操作を行う領域(上のXAMLでは Canvasオブジェクト)になります。そしてManipulationStartingイベントで、Manipulationコンテナを明示的に設定する必要があります。次に、ManipulationDeltaイベントで実際の操作を行うのです。このイベントハンドラを記述したコードを以下に示します。

 private void canvas_ManipulationStarting(
                     object sender, ManipulationStartingEventArgs e)
{
   // コンテナの設定
   e.ManipulationContainer = canvas;
   e.Handled = true;
}

private void canvas_ManipulationDelta(
                      object sender, ManipulationDeltaEventArgs e)
{
   // 操作対象のUI要素を取得
   var element = e.OriginalSource as UIElement;
   // 移動させるために MatrixTransformを取得
   var transform = element.RenderTransform as MatrixTransform;
   // 取得したMatrixTransformよりMatrixを取得します
   // (移動させるためのMatrixTransformを作成するためです)
   var matrix =
       transform == null ? Matrix.Identity : transform.Matrix;
   // 移動量を設定します(ScaleAt:ズーム、
   //                 RotateAt:回転、TranslateAt:移動)
   matrix.ScaleAt(e.DeltaManipulation.Scale.X,
                  e.DeltaManipulation.Scale.Y,
                  e.ManipulationOrigin.X, e.ManipulationOrigin.Y);
   matrix.RotateAt(e.DeltaManipulation.Rotation,
                   e.ManipulationOrigin.X, e.ManipulationOrigin.Y);
   matrix.Translate(e.DeltaManipulation.Translation.X,
                   e.DeltaManipulation.Translation.Y);
   // 移動します
   element.RenderTransform = new MatrixTransform(matrix);

   e.Handled = true;
}

上記のコードでは、パン、ズーム、ローテートを処理するためにXAMLにMatrixTransformオブジェクトを記述しています。最後にRenderTransformを設定することで実際の動作が行われます。ManipulationStartingイベントハンドラでは、e.ManipulationContainer = canvas と明示的にキャンバスオブジェクトを設定していますが、この例ではthis オブジェクトを設定しても問題はありません。 

ここでのポイントは、少ないコードでWin32 APIのManipulationProcessorと等価な動作をWPF4では、実現が可能だという点です。むしろ、WM_GestureメッセージをManipulationとして解釈するようになっていると理解した方が良いでしょう。