Google マップなんかでホイールを回すとズームする機能。あれって現在マウスが指している位置はそのままで拡大率だけが変わるのだが,同じことを実装しようとすると意外に難しい。忘れそうなのでメモ。
図は拡大率R1から拡大率R2に変更したときの例。大きな四角はマップ全体で,中の四角はビューポート。拡大率R1でマウスカーソルが絶対座標(Ax, Ay) 相対座標(Rx,Ry)にいるとき,拡大率をR2にした場合を考える。このとき,ビューのスクロール位置(Sx, Sy)をうまくいじってやれば,ビューからの相対座標(Rx,Ry)を変えずに拡大をすることができる。(Sx, Sy)の求め方は以下の通り。
(Sx', Sy') = (Ax', Ay') - (Rx, Ry) = (Ax, Ay) * R2 / R1 - (Rx, Ry)
でここからがWPFの場合だが,OnMouseWheelの中で以下のように実装してみた。_view(ScrollViewerなど)の中に_map(Canvasなど)を入れている前提。_viewへのGetPositionとスクロール位置はR1倍した値になり,_viewの中身へのGetPositionは拡大する前の値になるので上の式と若干異なっている。
private void OnMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e) { double a_x = e.GetPosition(_map).X; double a_y = e.GetPosition(_map).Y; double r_x = e.GetPosition(_view).X; double r_y = e.GetPosition(_view).Y; R_2 = R1 + e.Delta; _view.ScrollToHorizontalOffset(a_x * R_2 - r_x); _view.ScrollToVerticalOffset(a_y * R_2 - r_y); }