mikeo_410


コントロールの均等配置(フレーム周期の描画)

 Silverlight のサンプル
(ブラウザのウインドウサイズを変えて見てください)

 Silverlight のサンプル2
マウスホイールでのズーミング、パネルのドラッグを追加しました。

  「コントロールの動的配置」は、ContentPresenterクラスとインターバルタイマで自律的に移動するコントロールを作って見ました。今回は、フレーム周期のイベントで表示を更新する方法を試します。目的はコントロールを移動させながら均等に配置することです。配置しているのは単なる図ではなく、コントロールです。クリックのイベントを処理したり、いろいろなプロパティを設定することができます。

※ サンプル画像のフルーツ画像は、「WEB素材-Memory」の画像を使わせていただきました。
http://ao777.blog64.fc2.com/blog-category-24.html
から part1fruit.zip 中の png を列挙しています。
 

 動きながら  ==> 均等に並ぶ

1.目標と、何ができたか

1.1.均等配置

  概ね思うようになったのですが、目標はもっと強力なものです。
  上の図では、四隅が有効に使われていません。個々の図(マーク)の間隔が狭い場合は四隅も使って矩形に配置し、間隔が広い場合は円形に並ぶようになるのが理想的だと考えます。また、ウインドウを広げた場合、適切なマークの間隔以上には広がらないことも必要です。

1.2.ランダムな順序

  出来たものは、毎回マークの表示順が異なります。視覚的効果としては良いのですが、実用的なアプリケーションのボタンとして使うには順番が変わらないものも必要だと思います。
  必ずしも大きくマークが移動する必要はないので、納まるべき位置に向かって揺れて移動するような動作の方が実用的かもしれません。

1.3.ライブラリ化

  ネットワーク構造のデータを図示するようなライブラリを作りたいのですが、やっと動作の仕組みが分かってきたところで道は遠いようです。

2.動作の仕組み

2.1.PanelクラスとCompositionTarget.Rendering

  Panelを継承したクラスで、CompositionTarget.Renderingにハンドラを設定するとフレーム周期でハンドラが呼び出されるようになると言う仕組みを使います。
  フレーム周期ですが大まかに実測すると16msでした。モニタのリフレッシュレート由来と考えています。モニタのリフレッシュレートを 60 - 80Hz とすれば処理間隔は 12-17ms で、差を無視します。厳密な処理や、高性能モニタがあるならリフレッシュレートを読み取って処理する必要があるでしょう。
  負荷が高いときにはどうなるのかも気にはなりますが、見た目の問題だけなので追及しないことにします。

2.2.スタティックなイベントハンドラ登録

  インテリセンスの助けを借りて、
    CompositionTarget.Rendering +=
  まで入力して、Tabキーを2回押すとハンドラのメソッドまで作ってくれます。
  このメソッドで、マークの位置を移動します。

  ここで気が付いたのは、CompositionTargetはインスタンシングしたオブジェクトではないので、Rendering はスタティックだと言うことです。名前空間で1義の登録場所があると言うことです。
  このスタティックなイベントハンドラの登録場所を使う方法は、マークがクリックされたことをアプリケーションに通知するイベントハンドラの登録場所の確保にも利用できます。
  個々のマークにハンドラを設定しなくても良くなります。

2.3.マークの作り方

  「コントロールの動的配置」と同様、ContentPresenterクラスを継承して作ります。

3.アプリケーション

  Panel1 に描画処理を作ることにして、XAML の Grid に Panel1を割り当てます。
  また、マークがクリックされたとき、どのマークか分かるようにステータスバーに識別子を表示します。

  1. public partial class Window1 : Window
  2. {
  3.     public Window1()
  4.     {
  5.         InitializeComponent();
  6.     }
  7.     private void grid1_Loaded(object sender, RoutedEventArgs e)
  8.     {
  9.         Panel1 panel = new Panel1();
  10.         panel.Background = Brushes.Bisque;
  11.         grid1.Children.Add(panel);
  12.         MarkBase.MouseClickHandler += new MarkBase.MouseEvent(MarkBase_MouseClickHandler);
  13.     }
  14.     void MarkBase_MouseClickHandler(object sender, RoutedEventArgs e)
  15.     {
  16.         statusBar1.Text = ((MarkBase)sender).Identifier;
  17.     }
  18. }

  Panel1クラスは、Panelを継承します。ライブラリ化したい部分と未分化で、計算部分を除いてあります。要点は、コンストラクタでマーク図形のオブジェクトを作成、追加して、CompositionTarget.Renderingにハンドラを登録することです。
  ハンドラは、フレーム周期で呼び出され、各マークの表示位置を移動します。
  移動しなくなり、収束したらハンドラの登録を削除します。
  マークは、ContentPresenterクラスから派生したMarkBaseクラスを継承しているので、Childrenからマークを認識できます。

  1. class Panel1 : Panel
  2. {
  3.     public Panel1()
  4.     {
  5.         //英字の入った丸いマークを描画
  6.         string labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  7.         for (int i = 0; i < labels.Length; i++)
  8.         {
  9.             MarkBorder mb = new MarkBorder(labels.Substring(i, 1));
  10.             this.Children.Add(mb);
  11.         }
  12.         //フルーツのマークを描画
  13.         string[] fns = Directory.GetFiles(©"..\..\png", "*.png");
  14.         foreach (string fn in fns)
  15.         {
  16.             MarkImage mi = new MarkImage(fn);
  17.             this.Children.Add(mi);
  18.         }
  19.         //リサイズいベント処理の追加
  20.         this.SizeChanged += new SizeChangedEventHandler(Panel1_SizeChanged);
  21.         //フレーム周期のイベント処理を追加
  22.         if (compositionTargetRendering <= 0)
  23.         {
  24.             compositionTargetRendering++;
  25.             CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
  26.         }
  27.     }
  28.     //----------------------------------------------------------
  29.     //CompositionTarget.Renderingへのハンドラ設定を2重にしないように
  30.     int compositionTargetRendering = 0;
  31.     //----------------------------------------------------------
  32.     //リサイズ時に計算したウインドウ表示サイズに依存した値
  33.     Point panelCenter;      //パネルの中心の座標
  34.     double maxVectorLength; //パネルの中心からの距離の最大値
  35.     //----------------------------------------------------------
  36.     #region マークの位置を計算するメソッド
  37.     #endregion
  38.     //----------------------------------------------------------
  39.     //ウインドウのリサイズのイベント
  40.     void Panel1_SizeChanged(object sender, SizeChangedEventArgs e)
  41.     {
  42.         ChangePanelCenter();
  43.     }
  44.     //レンダリング・イベント
  45.     void CompositionTarget_Rendering(object sender, EventArgs e)
  46.     {
  47.         //MarkBase型を持つものだけ抽出。追加削除を考慮して毎回算出。
  48.         int mbCount = 0;
  49.         int[] index = new int[this.Children.Count];
  50.         for (int i = 0; i < this.Children.Count; i++)
  51.             if (this.Children[i] is MarkBase)
  52.                     index[mbCount++] = i;
  53.         Vector[,] F = new Vector[mbCount, mbCount];
  54.         for (int i = 0; i < mbCount; i++)
  55.         {
  56.             MarkBase i_mb = (MarkBase)this.Children[index[i]];
  57.             F[i, i] = new Vector();
  58.             for (int j = (i + 1); j < mbCount; j++)
  59.             {
  60.                 MarkBase j_mb = (MarkBase)this.Children[index[j]];
  61.                 F[i, j] = GetRepulsiveForce(RandomVecorIfZero(i_mb.Location - j_mb.Location), mbCount);
  62.                 F[j, i] = -F[i, j];
  63.             }
  64.         }
  65.         bool changed = false;
  66.         for (int i = 0; i < mbCount; i++)
  67.         {
  68.             MarkBase mb = (MarkBase)this.Children[index[i]];
  69.             Vector v = new Vector();
  70.             for (int j = 0; j < mbCount; j++)
  71.                 if (j != i) v += F[i, j];
  72.             v += GetSpringForce(RandomVecorIfZero(
  73.                 mb.Location - panelCenter), mbCount);
  74.             v += MaxMovement(mb.Location);
  75.             changed |= updateMark(mb, v);
  76.         }
  77.         if (!changed)
  78.         {
  79.             //変化が無くなったらレンダリングイベントを停止
  80.             CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
  81.             compositionTargetRendering--;
  82.         }
  83.     }
  84. }

  丸に英字のマークは、Borderクラスでできています。

  1. class MarkBorder : MarkBase
  2. {
  3.     public MarkBorder(string label)
  4.         : base(label)
  5.     {
  6.         this.Cursor = Cursors.Hand;
  7.         ContentPresenter CP = new ContentPresenter() { Content = label };
  8.         base.Content = new Border()
  9.         {
  10.             Background = Brushes.Aquamarine,
  11.             Padding = new Thickness(5),
  12.             CornerRadius = new CornerRadius(16),
  13.             BorderThickness = new Thickness(2),
  14.             BorderBrush = new SolidColorBrush(Colors.Gray),
  15.             Child = CP
  16.         };
  17.     }
  18. }

  果物のマークは、Imageクラスでできています。

  1. public MarkImage(string src)
  2.     : base(src)
  3. {
  4.     this.Cursor = Cursors.Hand;
  5.     Image img=new Image();
  6.     BitmapImage bm = new BitmapImage();
  7.     bm.BeginInit();
  8.     bm.UriSource = new Uri(src, UriKind.Relative);
  9.     bm.CacheOption = BitmapCacheOption.OnLoad;
  10.     bm.EndInit();
  11.     img.Width = 32;
  12.     img.Height = 32;
  13.     img.Source = (BitmapSource)bm;
  14.     base.Content = img;
  15. }


mikeo_410@hotmail.com