[xaml/C#] 枠の中で画像などを拡大縮小する（マウスと指で移動/拡大）

**もくじ**
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f

**回転/拡大縮小/移動の関連記事**

- [画面の要素を回転/拡大縮小/移動する（RenderTransform）](https://qiita.com/tera1707/items/b6a1803a64f34827f741)
- [画面の要素を回転/拡大縮小/移動する（RenderTransformにMatrixTransform）](https://qiita.com/tera1707/items/5d02ca57f692283a0f9a)
- [フリックやピンチインアウトでControlを移動・拡大縮小する(ManipulationDeltaイベントとMatrixTransform)](https://qiita.com/tera1707/items/dff76c2b870fe5c63e9d)
- [[xaml/C#] 枠の中で画像などを拡大縮小する（マウスと指で移動/拡大）](https://qiita.com/tera1707/items/37af056540f23e73213f)


# やること

ウインドウの中の、ある枠のなかで、写真などを拡大縮小したい。
窓の外のモノを動かしているイメージ。

窓枠の中ではそのコントロール(例ではTextBlock)が見えてるが、枠の外側にドラッグやフリックして移動させると、枠の外は見えなくなる感じにしたい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/199119ff-aa27-abe7-9861-0bf3686f8b70.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/8e18e2f5-d7cf-8b5b-7914-c2b1774c6c69.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/f2ffd634-4ebb-546b-5f95-44411877ed60.png)
こんな感じ。


# やりかた
以前の記事(上の「回転/拡大縮小/移動の関連記事」のところを参照)でやった、コントロールの移動、拡大縮小を、`ScrlollViewer`の中でやればOK。


※今回は、指で移動/拡大縮小と同時に、マウスで移動/拡大縮小ができるようにした。

# サンプル

```mainWindow.xaml
<Window x:Class="WpfApp39.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp39"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid ShowGridLines="True">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="3*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="3*"/>
                <RowDefinition Height="1*"/>
            </Grid.RowDefinitions>

            <ScrollViewer Name="MyScrollViewer" Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
                          IsManipulationEnabled="True"
                          ManipulationDelta="Grid_ManipulationDelta"
                          PreviewMouseLeftButtonDown="MyGrid_MouseLeftButtonDown"
                          MouseLeftButtonUp="MyGrid_MouseLeftButtonUp"
                          MouseMove="MyGrid_MouseMove"
                          MouseLeave="MyGrid_MouseLeave"
                          PreviewMouseWheel="MyGrid_MouseWheel">
                
                <!-- このテキストBlockを回転させる -->
                <!-- ここに、位置の基準点を指定するHorizontalAlignmentとかRenderTransformOrigin、-->
                <!-- 大きさを指定するWidthやHeightを指定すると、拡大するときの中心点がずれてしまう-->
                <TextBlock x:Name="MyTarget" Text="あ" FontSize="160" Background="Pink" />
            </ScrollViewer>
        </Grid>
    </Grid>
</Window>

```

```MainWindow.xaml.cs
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApp39
{
    public partial class MainWindow : Window
    {

        /// マウス押下中フラグ
        bool isMouseLeftButtonDown = false;

        /// マウスを押下した点を保存
        Point MouseDonwStartPoint = new Point(0, 0);

        /// マウスの現在地
        Point MouseCurrentPoint = new Point(0, 0);

        public MainWindow()
        {
            InitializeComponent();

            Debug.WriteLine($"大きさ：({MyTarget.ActualWidth},{MyTarget.ActualHeight})");
        }

        /// 【指】
        /// 移動時のイベント
        private void Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            var delta = e.DeltaManipulation;
            Matrix matrix = (MyTarget.RenderTransform as MatrixTransform).Matrix;
            matrix.Translate(delta.Translation.X, delta.Translation.Y);

            var scaleDelta = delta.Scale.X;
            var orgX = e.ManipulationOrigin.X;
            var orgY = e.ManipulationOrigin.Y;
            matrix.ScaleAt(scaleDelta, scaleDelta, orgX, orgY);
            MyTarget.RenderTransform = new MatrixTransform(matrix);
        }

        /// 【マウス】
        /// マウス押下
        private void MyGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // クリックした位置を保存
            // 位置の基準にするControlはなんでもいいが、MouseMoveのほうの基準Controlと合わせること。
            MouseDonwStartPoint = e.GetPosition(MyScrollViewer);

            isMouseLeftButtonDown = true;
        }

        /// 【マウス】
        /// マウス離す
        private void MyGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            isMouseLeftButtonDown = false;
        }

        /// 【マウス】
        /// マウスがコントロールの上から外れた
        private void MyGrid_MouseLeave(object sender, MouseEventArgs e)
        {
            isMouseLeftButtonDown = false;
        }

        /// 【マウス】
        /// マウスがコントロールの上を移動
        private void MyGrid_MouseMove(object sender, MouseEventArgs e)
        {
            if (isMouseLeftButtonDown == false) return;

            // マウスの現在位置座標を取得（ScrollViewerからの相対位置）
            // ここは、位置の基準にするControl(GetPositionの引数)はScrollViewrでもthis(Window自体)でもなんでもいい。
            // Start時とマウス移動時の差分がわかりさえすればよし。
            MouseCurrentPoint = e.GetPosition(MyScrollViewer);

            // 移動開始点と現在位置の差から、MouseMoveイベント1回分の移動量を算出
            double offsetX = MouseCurrentPoint.X - MouseDonwStartPoint.X;
            double offsetY = MouseCurrentPoint.Y - MouseDonwStartPoint.Y;

            // 動かす対象の図形からMatrixオブジェクトを取得
            // このMatrixオブジェクトを用いて図形を描画上移動させる
            Matrix matrix = ((MatrixTransform)MyTarget.RenderTransform).Matrix;

            // TranslateメソッドにX方向とY方向の移動量を渡し、移動後の状態を計算
            matrix.Translate(offsetX, offsetY);

            // 移動後の状態を計算したMatrixオブジェクトを描画に反映する
            MyTarget.RenderTransform = new MatrixTransform(matrix);

            // 移動開始点を現在位置で更新する
            // （今回の現在位置が次回のMouseMoveイベントハンドラで使われる移動開始点となる）
            MouseDonwStartPoint = MouseCurrentPoint;
        }

        /// 【マウス】
        /// ホイールくるくる
        private void MyGrid_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            var scale = 1.0;
            Matrix matrix = ((MatrixTransform)MyTarget.RenderTransform).Matrix;

            // ScaleAt()の拡大中心点(引数3,4個目)に渡すための座標をとるときの基準Controlは、拡大縮小をしたいものの一つ上のControlにすること。
            // ここでは拡大縮小するGridを包んでいるScrollViewerを基準にした。
            MouseCurrentPoint = e.GetPosition(MyScrollViewer);

            // ホイール上に回す→拡大 / 下に回す→縮小
            if (e.Delta > 0) scale = 1.25;
            else scale = 1 / 1.25;

            Debug.WriteLine($"倍率：{scale} 中心点：{MouseCurrentPoint} 大きさ：({MyTarget.ActualWidth},{MyTarget.ActualHeight})");

            // 拡大実施
            matrix.ScaleAt(scale, scale, MouseCurrentPoint.X, MouseCurrentPoint.Y);
            MyTarget.RenderTransform = new MatrixTransform(matrix);
        }
    }
}

```

# 備考
上のサンプルコードには書いてないが、拡大縮小をした後の現在の拡大率がいくつか知りたい場合は`matrix.M11`の値を見ればわかる。(githubのコード参照)

# コード
https://github.com/tera1707/WPF-/tree/master/032_ScaleTranslateByMatrix

# 参考
**アフィン行列について**
ここを見て、倍率がmatrix.M11から取れると知った
http://math-edu-lab.sakura.ne.jp/embed/afin.html