2015年1月3日土曜日

ScrollViewer で中心に向けてZoomIn

WinRT の ScrollViewer で、Codeから拡大率を増減する場合…

ChangeView( double horizontalOffset, double verticalOffset, float zoomFactor)

を使います。
horizontal/verticalOffsetをNullにするとOffsetは0、画像左上をScrollViewerの左上に合わせた状態、になりますよと。

では、所謂「普通の」拡大縮小…ScrollViewer で今見ている物の中心を維持したまま拡大縮小するには?というのがこの項の話です。
これが実は結構面倒でして。完成しているものがこちらになります。(PICT8 で使っています)


 

        private void changeViewWithKeepCurrentCenter(ScrollViewer currentSv, float newZoomFactor)
        {
            // SVのProperty解説
            // Viewport    SVのサイズ、表示エリア
            // Extent      表示されてるイメージサイズ(クリップされてるとこも含む,のでアスペクト比は一定)
            //             VPに対して余りがある場合そこは含まれない あくまでイメージのサイズ 
            //             ZoomFactor1なら元画像サイズと同じになる .5なら半分、2なら倍
            // H/VOffset   Extentベースの表示オフセット値

            // つまり
            // 1) 今表示している部分の中心座標が、元画像の何処にあたるか算出して
            // 2) 新しい倍率から新しいExtentのサイズを出して
            // 3) 1)で出した元の中心とExtentから新しいOffsetを算出
            // 元画像サイズを算出
            double originalWidth = currentSv.ExtentWidth / currentSv.ZoomFactor;
            double originalHeight = currentSv.ExtentHeight / currentSv.ZoomFactor;

            // 元画像スケールでの現表示の中心位置を算出する
            double originalCenterX = 0;
            double originalCenterY = 0;
            
            if( currentSv.ViewportWidth < currentSv.ExtentWidth )
            {
                // 余りが無い状態
                // 「余り」とは、ExtentがViewportより小さくなっていて、SVが額縁をつけてる状態
                double eCenterX = currentSv.HorizontalOffset + currentSv.ViewportWidth / 2;
                // originalに変換
                originalCenterX = eCenterX / currentSv.ZoomFactor;
            }
            else
            {
                // 余りアリ
                double eCenterX = currentSv.HorizontalOffset + currentSv.ExtentWidth / 2;
                originalCenterX = eCenterX / currentSv.ZoomFactor;
            }

            if (currentSv.ViewportHeight < currentSv.ExtentHeight)
            {
                // 余りが無い状態
                double eCenterY = currentSv.VerticalOffset + currentSv.ViewportHeight / 2;
                originalCenterY = eCenterY / currentSv.ZoomFactor;
            }
            else
            {
                // 余りアリ
                double eCenterY = currentSv.VerticalOffset + currentSv.ExtentHeight / 2;
                originalCenterY = eCenterY / currentSv.ZoomFactor;
            }

            // ZoomFactor変更後の数値を出す

            // 新倍率ベースでの、位置の表示中心
            double newExtentCenterX = originalCenterX * newZoomFactor;
            double newExtentCenterY = originalCenterY * newZoomFactor;

            // 新倍率ベースでの、Extentのサイズ
            double newExtentWidth = originalWidth * newZoomFactor;
            double newExtentHeight = originalHeight * newZoomFactor;

            // 新倍率ベースでのオフセットは、中心 - Extentサイズの半分
            // (ここのOffset計算では余り、ExtentとViewportサイズの差は気にしなくてよい SVが勝手にやること)
            double newExtentOffsetX = newExtentCenterX - currentSv.ViewportWidth / 2;
            double newExtentOffsetY = newExtentCenterY - currentSv.ViewportHeight / 2;
            // デキター
            currentSv.ChangeView(newExtentOffsetX, newExtentOffsetY, newZoomFactor, false);

        }




出来上がってるのを見ると…当たり前じゃんかと言いたくなりますが…それはさておき。
これをコピペすれば動くはずです。

せっかくですので、以下はScrollViewer のプロパティについて簡単な説明です。


ScrollViewer の三つのプロパティ…Offset, ViewportWidth/Height, ExtentWidth/Height

ScrollViewer のProperties の内、数値として取れるスクロール・位置関連のプロパティは4つで、


  • ViewportWidth/Height ... ScrollViewer が開けている口、表示領域のサイズ。拡大縮小やスクロールでこのサイズは変わりません。アプリ領域変更等では変わります。
  • ExtentWidth/Height ... ScrollViewer に載せているContent の、「表示上の」サイズ。倍率に応じてこのサイズは変わります。倍率1なら元Content と同じサイズ、倍率2なら2倍。また、この値はScrollViewer のViewport で表示しきれない、Clip されている部分も含んだサイズです。
  • Horizontal/Vertical Offset ... Extent の左上(0, 0)から数えた、現在Viewport 上に表示している領域のオフセット。ここでの単位は「Extent での」、つまり表示画面上での単位であることに注意してください(元画像の単位では無い)。
  • ZoomFactor ... ScrollViewer の現在の倍率。ちなみにこれだけ何故かFloat なので注意(他は全部double )。


上3つ、特にHorizontal /Vertical Offset については、常に画面表示上のスケールであることに注意してください。つまり、何か計算しないと元画像の幅・高さやOffset 値は出てこないよ、ということです。

また上のCode Snippetでも少し触れていますが、Viewport よりExtent が小さい場合には少し注意が必要です。

ViewportよりExtentが小さくなってる場合

この場合、Horizontal/Vertical Offset は両方常に0 です。この状態ではPhysicalにはOffsetあるように見えはしますが、Logicalには常に(0, 0)、左右・上下の枠は勝手にScrollViewer がつけるもので、Contentは常に画面中心に表示されます。このため、表示中の画面中心を算出するには場合分けが必要です。

以上です。
ではでは楽しいScrollViewer Lifeをお過ごしください。


0 件のコメント:

コメントを投稿