tmytのらくがき

個人の日記レベルです

Silverlight for Windows Phone 7 Toolkitが便利すぎるのでもうひとつ

トランジションがあまりにも便利すぎる。というのは書きましたが、このToolkitほかにもめちゃめちゃ便利なのがあってそっちも紹介したいのでもうひとつ。
このToolkitに入ってるのはほとんどコントロールばっかりなんですけど、コントロールじゃないのがTransitionともうひとつGestureListenerってのがあります。
今回はこのGestureListenerを紹介。
今回のToolkitがリリースされるまでWP7でジェスチャーを認識する方法はあまりなくて、有志の公開しているライブラリを利用するか、がんばって自分で書くしかない感じでした。
シングルポイントでのフリックの検出はけっこう楽ちんで、各コントロールにManipulationStarted/ManipulationDelta/ManipulationCompleted 各イベントハンドラで検出できます。
でもってマルチポイントのジェスチャ、ピンチとかローテーションとかを検出するにはTouch.FrameReported イベントをハンドリングして、がんばらないといけませんでした。コードだとこんな感じで。

MultiScaleImage ZoomImage;
bool duringDrag = false;
Point zoomCenter;
Touch.FrameReported += new TouchFrameEventHandler((sender, e) =>
{
    TouchPointCollection tc =e.GetTouchPoints(ZoomImage);
    if (tc.Count == 2)
    {
        if (!duringZoom)
        {
            duringZoom = true;
            zoomDistance = Math.Sqrt(Math.Pow(tc[0].Position.X - tc[1].Position.X, 2) + Math.Pow(tc[0].Position.Y - tc[1].Position.Y, 2));
            zoomCenter = new Point((tc[0].Position.X + tc[1].Position.X) / 2, (tc[0].Position.Y + tc[1].Position.Y) / 2);
            return;
        }
        double z = Math.Sqrt(Math.Pow(tc[0].Position.X - tc[1].Position.X, 2) + Math.Pow(tc[0].Position.Y - tc[1].Position.Y, 2));
        zoomDistance / z;
    }
    else
    {
        if (duringZoom)
        {
            duringZoom = false;
            return;
        }
    }
});

こんな感じに自分で複数点探してきて、それの距離を自分でとか非常にめんどくさいのですがこれがGestureListenerですべて解決できます。
とりあえずよくありそうなImage を回転させたり拡大させたりをやってみます。
参照の追加とかネームスペースの追加とかはSilverlight for Windows Phone 7 Toolkitが公開されました - tmytのらくがき みたいにやってみてください。

画像ファイルを適当にプロジェクトに追加してXAMLにこんな感じに。

<Image Source="Tulips.jpg" RenderTransformOrigin="0.5,0.5">
    <Image.RenderTransform>
        <TransformGroup>
            <RotateTransform x:Name="rotate" />
            <ScaleTransform x:Name="scale" />
        </TransformGroup>
    </Image.RenderTransform>
</Image>

これで画像が表示されましたよね、次にToolkitのGestureListenerを使ってピンチなどのジェスチャをハンドリングできるようにします。
GestureListenerを使うにはジェスチャを受けたいエレメントの直下にGestureService.GestureListener ....といろいろ書きます。さっきのImageにこれを追加すると次のような感じ。

<Image Source="Tulips.jpg" RenderTransformOrigin="0.5,0.5">
    <toolkit:GestureService.GestureListener>
        <toolkit:GestureListener 
            PinchStarted="GestureListener_PinchStarted"
            PinchDelta="GestureListener_PinchDelta"
            />
    </toolkit:GestureService.GestureListener>
    <Image.RenderTransform>
        <TransformGroup>
            <RotateTransform x:Name="rotate" />
            <ScaleTransform x:Name="scale" />
        </TransformGroup>
    </Image.RenderTransform>
</Image>

toolkit:GestureListener のところでジェスチャに関するイベントをハンドリングできます。とりあえず今回はPinchStarted とPinchDelta だけをハンドリングしました。
ハンドリングできるイベントにはほかにも、タップ、ダブルタップ、ドラッグ、ホールド、フリックがあります。
XAML側はこれでおしまい。次にコードビハインドにイベントハンドラを書きますがとっても簡単で次のような感じ。

double angleInitial;
double scaleInitial;

private void GestureListener_PinchStarted(object sender, PinchStartedGestureEventArgs e)
{
    angleInitial = 0;
    scaleInitial = 1;
}

private void GestureListener_PinchDelta(object sender, PinchGestureEventArgs e)
{
    rotate.Angle += (e.TotalAngleDelta - angleInitial);
    scale.ScaleX = scale.ScaleY /= (scaleInitial / e.DistanceRatio);
    angleInitial = e.TotalAngleDelta;
    scaleInitial = e.DistanceRatio;
}

PinchStarted で初期化して、PinchDelta で実際に拡大や回転をしています。ピンチといいながらもピンチイン・ピンチアウトだけでなく、回転も処理できます。
e.TotalAngleDelta にはPinchStarted から実際に回転した角度の合計が、e.DistanceRatio にはPinchStarted の時の2点の距離と現在の2点の距離の比率が入っているので例のようにてきとうに計算するといいと思います。
以上でコードビハインド側もできあがりで、あとは実行するだけで2本指でのジェスチャが使えます。
指の位置と距離がここだから...といった感じの幾何計算はぜんぶToolkitがめんどうみてくれます。ほんとらくちん。
ImageをRenderTransform で変換すると実際あんまり速くはないですが、おてがるな例なので今回はこれを紹介しました。
TransitionやGestureListenerなどSilverlight for Windows Phone 7 Toolkitにはめちゃめちゃ便利なのがいっぱい入ってるのでWP7開発するなら是非使ってみてください