Ctrl+Wheelで拡大縮小するWPFImageコントロール

今日は小ネタ。Ctrl+マウスホイールで拡大縮小するよくあるUIです。

画像を表示するのでImageコントロールの派生で作ってみます。

ZoomImage.cs

namespace ZoomableImage
{
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Input;

    public class ZoomImage : Image
    {
        private TransformGroup _transformGroup = new TransformGroup();
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            if ((Keyboard.Modifiers & ModifierKeys.Control) != ModifierKeys.None)
            {
                ChangeScale(e.GetPosition(this), e.Delta);
            }
            base.OnMouseWheel(e);
        }
        private void ChangeScale(Point center, int delta)
        {
            double scale = (0 < delta) ? 1.1 : (1.0 / 1.1);
            _transformGroup.Children.Add(new ScaleTransform(scale, scale, center.X, center.Y));
            RenderTransform = _transformGroup;
        }
    }
}

XAMLだとTransformGroupとか意識しませんがコードで書くとこのようになります。1つ1つの処理をGroup化できるので楽ですね、おまけに自然とコマンドパターンになっているのでUndo/Redoが簡単に実現できます。これをWindowに張り付けて、適当な画像をセットすればとりあえず動作開始。

MainWindow.xaml

<Window x:Class="ZoomableImage.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:ZoomableImage"
        Title="MainWindow" Height="260" Width="350">
    <Grid>
        <my:ZoomImage Width="200" Height="100" Source="testimage.png"/>
    </Grid>
</Window>

起動状態は通常サイズの画像が表示されており、

first
これがCtrl+マウスホイールすると・・・・
zoomed
こんな感じに。

今回はGridの上で簡単に動くモノとしてはこれで良いかと思いますが、原点位置を精緻にコントロールしたい場合や他のレイアウトコントロールに配置する場合には注意が必要です。基本的にはマウス位置を中心に拡大・縮小してくれることを期待したいですが、等倍状態からの拡大縮小は正しく動作しますが拡大or縮小状態で中心がずれた位置でさらなる拡大or縮小をすると今回のソースではダメです。

また他のレイアウトコントロールに配置する場合ですが、例えばStackPanelに配置する場合を考えてみるとこのまま後ろにボタンを追加してしまうとズーム動作が変になります。

onstackpanel

これはTransformGroupを設定しているのがRenderTransformだからで、描画のためだけの変換処理なのです。実際にコントロールの配置を動的にレイアウトしなおしたい場合はLayoutTransformに設定する必要があります。そうすれば以下のようになります。

layouttransform

トライアンドエラーで習熟が必要ですね。

コメントを残す