mikeo_410


図形のズーム

  図形のズームをして見ます。見えている部分が拡大されるだけでなく、スライダーで全域が見ることができるようにします。上にあるスライダーで拡大率を調整します。右、下のスライダーで位置を移動します。

1.ズームのイメージ 


  左図は、長さを2倍に拡大するイメージです。
  Canvas を拡大することでズームを実現します。
  Canvas のサイズは、ActualWidth、ActualHeight で知ることができます。
  設定する場合は、Width、Height に設定します。

  実際に見えている範囲は、ScrollViewer が管理しています。
  ScrollViewer には、ActualWidth、ActualHeight の他に ViewportWidth、ViewportHeight があります。
  ActualWidth、ActualHeight は、スクロールバーを除いたサイズを表し、ViewportWidth、ViewportHeight は、スクロールバーを含んだサイズを返すようです。

  Canvas と図形を拡大しただけだと、Canvas の左上隅が表示され、図が右下方に移動してしまいます。拡大前後で見えている範囲の中心が変わらないようにするには、拡大と同時にスクロールすることが必要です。図の①、②は、ScrollToHorizontalOffset()、ScrollToVerticalOffset()で、設定できます。

2.視野の移動

  1. 図をクリックしたら、その位置を表示の中心になるように移動する。
  2. 拡大率が変更されたら、中心が移動しないように、ScrollViewer のオフセットを調整する。

  と、言うようにします。

3.クリックによる移動

3.1.MouseLeftButtonDown イベント

   Canvas の MouseLeftButtonDown イベントで、マウスポインタの位置を取得します。
   Point point = e.GetPosition(canvas1);
   のようにしてCanvasに対する位置を取得します。

3.2.マウスイベントとBackground

  海の上でクリックしてもマウスイベント処理が実行されないことに気が付きました。
  陸は、Polygon の Fill で塗っていましたが、これをやめると、陸でもマウスイベント処理が実行されません。線上だけがイベントになります。
  ドキュメントには、「ポインタがこの要素上にあるときに」とあり、要素上かどうかが塗りつぶしと関連しているようです。

  1. 陸を塗りつぶさないと線上だけでイベントが発生する。
  2. 陸を塗りつぶすと、陸でもイベントが発生する。
  3. Canvas の Background を指定すると海でもイベントが発生する。
  4. マウスで図上にマークを書こうと、Canvas をもう一つ重ねてみた。
    (0,0)にクロスマークを書き、TranslateTransform で(100,100) に表示した。
    Background を指定しなければ全域でイベントが発生する。
  5. Background に透明に設定すると、(0,0)-(*,100)、(0,0)-(100,*)だけでイベントが発生する。

  5.だけはまったく理解できないが、Background や Fill がマウスイベントと関連していることは間違いないようです。また、透明を指定することは、指定しないことにはならないようです。
  今回は、全域でイベントが欲しいので、

  1. Canvas の Background を指定する。
  2. 陸はFillする。

3.3.マウスポインタの位置を中心に表示する


  イベントの処理で、Point cp = e.GetPosition(canvas1); のように Canvas 上でのポイントが取得できます。

  この値と、ScrollViewer のサイズから、ScrollViewer のオフセットを計算すれば良いことになります。

  この計算は、スクロールバーの幅を含んだ中心の位置に合わせることになります。
  右下隅の拡大表示が見にくい場合もあるかもしれません。

  Canvas から ScrollViewer がはみ出さないように制御します。
  Canvas 上の位置が、ScrollViewer の 幅、高さの1/2 より小さい場合は、移動しないことにします。
  また、Canvas の幅からオフセットの値を引いた差が、ScrollViewer の 幅の1/2以下にならないように制御します。
  高さについても同様に制御します。 

  1. //Canvas上の指定座標が表示の中心になるようにするサブルーチン
  2. void ViewCenter(double center_x, double center_y)
  3. {
  4.     double sv_width_2 = scrollViewer1.ActualWidth / 2;
  5.     double sv_height_2 = scrollViewer1.ActualHeight / 2;
  6.     double offset_x = center_x - sv_width_2;
  7.     double offset_y = center_y - sv_height_2;
  8.     if (offset_x < 0) offset_x = 0;
  9.     else if ((canvas1.ActualWidth - offset_x) < sv_width_2)
  10.         offset_x = canvas1.ActualWidth - sv_width_2;
  11.     if (offset_y < 0) offset_y = 0;
  12.     else if ((canvas1.ActualHeight - offset_y) < sv_height_2)
  13.         offset_y = canvas1.ActualHeight - sv_height_2;
  14.     scrollViewer1.ScrollToHorizontalOffset(offset_x);
  15.     scrollViewer1.ScrollToVerticalOffset(offset_y);
  16. }
  17. //スライダーの移動による拡大率の変更
  18. private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
  19. {
  20.     double f = 1 + e.NewValue;
  21.     if (radioButton_vector.IsChecked == true)
  22.     {
  23.         double sv_width_2 = scrollViewer1.ActualWidth / 2;
  24.         double sv_height_2 = scrollViewer1.ActualHeight / 2;
  25.         double center_x = scrollViewer1.HorizontalOffset + sv_width_2;
  26.         double center_y = scrollViewer1.VerticalOffset + sv_height_2;
  27.         double ratio = canvas1.ActualWidth;
  28.         ReDraw(f);
  29.         ratio = canvas1.Width / ratio;
  30.         ViewCenter(center_x * ratio, center_y * ratio);
  31.     }
  32.     else
  33.     {
  34.         scaleTransform1.ScaleX = f;
  35.         scaleTransform1.ScaleY = f;
  36.     }
  37. }
  38. //マウスの左ボタンのクリック
  39. private void canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  40. {
  41.     Point cp = e.GetPosition(canvas1);
  42.     ViewCenter(cp.X, cp.Y);
  43. }

3.4.拡大率が変わった時、中心を維持する

3.4.1.スライダーと拡大率

  スライダーの ValueChanged イベントで e.NewValue の値を見ると 0 から 10 の値が得られます。拡大率は1以上とするので、1を加算して拡大率とします。

3.4.2.現在の中心の位置は

  拡大率を変更する前の、現在の中心は、ScrollViewer の HorizontalOffset、VerticalOffset に、ScrollViewer の窓の幅、高さの1/2をそれぞれ加算すれば得られます。
  この座標に倍率を掛けて、前述の「マウスポインタの位置を中心に表示する」と同じように、ScrollViewer のオフセットを設定すれば良さそうです。

4.Silverlight との差異

  1. WPF では、LayoutRoot の Loaded イベントでScrollViewer のサイズが確定している。Silverlight ではゼロなので、SizeChanged イベントで最初の描画が行われるようにした。
  2. WPF では、MouseWheel イベントは、ScrollViewer の縦スクロールに使われている。Silverlight では、ブラウザの縦スクロールになる。
    WPF では、MouseWheel イベントを設定しても、縦スクロール動作は変わらない。Silverlight のサンプル2 のようにすればズームに割り当てられる。
     


mikeo_410@hotmail.com