mikeo_410


 1つのStyle記述から複数のインスタンス

  System.Windows.Controls.DataVisualization.Charting のChartで折れ線を書く場合、折れ線上に書かれるマークをStyleで設定します。
  マークの形状は少数ですが、サイズと色はいろいろです。サイズは、基本的に線幅から計算します。色は、線に指定した色を使います。
  したがって、すべてをあらかじめXAMLで記述して置くことはできません。

  形をXAMLで記述して置き、サイズや色を実行時に変えたStyleのインスタンスを作る方法が必要です。
  XAMLで記述したStyleは、実行時にResources[]から取り出せますが、これはStyleのインスタンスで、参照のたびにインスタンスが作られるわけではありません。
  1つのインスタンスでは、前に引いた線のマークまで変わってしまします。
  また、一度FrameworkElementのインスタンスのStyleプロパティに使うとシールされて変更できなくなります。

XAML記述を文字列としてビルドする

  以下のような関数で、XAML文字列を作成します。

  1. static string xaml_start =
  2.     @"<ControlTemplate " +
  3.          "xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " +
  4.          "xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>" +
  5.          "<Grid>";
  6. static string xaml_end =
  7.         "</Grid>" +
  8.     "</ControlTemplate>";
  9. //---------------------------------------------------------------------------
  10. // 丸いマーク用のXAML
  11. //---------------------------------------------------------------------------
  12. static string circle_xaml(double width, double height, Color color, bool fill)
  13. {
  14.     string s = xaml_start;
  15.     s += @"<Ellipse Width='" + width + @"' "
  16.        + @"Height='" + height + @"' "
  17.         + @"Stroke='" + color + @"' "
  18.         + @"StrokeThickness='1' ";
  19.     if (fill)
  20.     {
  21.         s += @"Fill='" + color.ToString() + @"' ";
  22.     }
  23.     s += @"/>" + xaml_end;
  24.     return s;
  25. }

   これを、読み込んで毎回新しいStyleインスタンスを作ります。

  1.         static ControlTemplate LoadXaml(string s)
  2.         {
  3. #if SILVERLIGHT
  4.             return (ControlTemplate)XamlReader.Load(s);
  5. #else
  6.             MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(s));
  7.             return (ControlTemplate)XamlReader.Load(stream);
  8. #endif
  9.         }

  この方法は、XAMLの編集が、エディタの支援を受けられないので少量の場合向きです。

Application.LoadComponent を使う

  実行時にXAML記述を読み込む方法ですが、実行形式以外にファイルが必要なのは不便なので Visual Studio が実行形式に組み込む方法が必要です。
  また、エディタの入力支援がないとほとんど記述できません。

  この方法は、ビハインドコードを伴わない単独のXAMLファイルをプロジェクトに追加します。
  Visual Studio は、これを実行形式に組み込んでくれます。
  これを、実行時にApplication.LoadComponent()で読み込むことができます。
  この機能は、FrameworkElementを作るもので、XAML記述のルートは、FrameworkElementである必要があります。

  ここでは、ContentControlにして見ました。
  その、ResourcesにStyleを記述します。
  Styleには、形状を決めるTempleteプロパティのみを記述します。
  サイズやカラーは、TemplateBindingで設定します。

  丸のマークを定義したXAMLファイルは以下のようです。

  1. <ContentControl  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2.                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.                     xmlns:u="clr-namespace:SilverlightApplication1">
  4.     <ContentControl.Resources>
  5.         <Style x:Key="mark" TargetType="u:Mark">
  6.             <Setter Property="Template">
  7.                 <Setter.Value>
  8.                     <ControlTemplate>
  9.                         <Ellipse Fill="{TemplateBinding Background}"
  10.                              Width="{TemplateBinding Width}"
  11.                              Height="{TemplateBinding Height}"/>
  12.                     </ControlTemplate>
  13.                 </Setter.Value>
  14.             </Setter>
  15.         </Style>
  16.     </ContentControl.Resources>
  17. </ContentControl>

  この記述から、毎回新しいStyleを返すために以下のようにしました。

  1. // マークのベースクラス
  2. public class MarkStyleBase
  3. {
  4.     // コンストラクタ
  5.     public MarkStyleBase(string xaml_file_name, double width, Color color)
  6.     {
  7.         // 同じ形のマークは1回だけロードするように
  8.         if (cc == null)
  9.         {
  10.             cc = new ContentControl();
  11.             Uri resourceLocater
  12.                 = new Uri("/SilverlightApplication1;component/" + xaml_file_name,
  13.                           System.UriKind.Relative);
  14.             Application.LoadComponent(cc, resourceLocater);
  15.             Debug.WriteLine("MarkStyleBase()");
  16.         }
  17.         // リソースからTemplateの定義を取り出して、新しいStyleを作る
  18.         Style style = (Style)cc.Resources["mark"];
  19.         MarkStyle = new Style(style.TargetType);
  20.         MarkStyle.Setters.Add(new Setter(
  21.             ((Setter)style.Setters[0]).Property, 
  22.             ((Setter)style.Setters[0]).Value));
  23.         // 色を設定
  24.         MarkStyle.Setters.Add(new Setter(
  25.             ContentControl.BackgroundProperty, new SolidColorBrush(color)));
  26.         // サイズを設定
  27.         MarkStyle.Setters.Add(new Setter(
  28.             ContentControl.WidthProperty, width * 10));
  29.         MarkStyle.Setters.Add(new Setter(
  30.             ContentControl.HeightProperty, width * 10));
  31.     }
  32.     virtual protected ContentControl cc { get; set; }
  33.     public Style MarkStyle { get; set; }
  34. }
  35. // 丸いマーク
  36. public class CircleStyle : MarkStyleBase
  37. {
  38.     public CircleStyle(double line_width, Color line_color)
  39.         : base("CircleMark.xaml", line_width, line_color)
  40.     {
  41.     }
  42.     static ContentControl _cc = null; // スタティック
  43.     override protected ContentControl cc
  44.     { get { return _cc; } set { _cc = value; } }
  45. }
  46. // Xマーク
  47. public class CrossStyle : MarkStyleBase
  48. {
  49.     public CrossStyle(double line_width, Color line_color)
  50.         : base("CrossMark.xaml", line_width, line_color)
  51.     {
  52.     }
  53.     static ContentControl _cc = null; // スタティック
  54.     override protected ContentControl cc
  55.     { get { return _cc; } set { _cc = value; } }
  56. }

  CircleStyleクラス、CrossStyleクラスがあり、これをコンストラクトし、MarkStyleプロパティに設定されたStyleを使います。
  XAMLを読み込むことで作られるContentControlのインスタンスは、スタティックな変数に割り当て読み込むのは1回にします。
  この中のResource[]のStyleを使いますが、Templateプロパティだけを取り出して使います。
  Templateプロパティにあるオブジェクトは、すべて同じインスタンスがセットされることになります。
  Styleは、CircleStyleクラス、CrossStyleクラスが new されるたびに新しいインスタンスを返しますが、そのTemplate部分は同じオブジェクトです。
  サイズと色に関する設定は、Styleの個々のインスタンスに固有です。

  このスタイルを割り当てる Markクラスは以下のようです。

  1. public class Mark : Control
  2. {
  3.     public Mark()
  4.     {
  5.     }
  6. }

  折れ線グラフの1つのデータ列(1つの折れ線)を表すクラスのつもりで以下のクラスを用意しました。

  1. public class DataPoints : FrameworkElement
  2. {
  3.     public Point[] Points { get; set; }
  4.     public double LineWidh { get; set; }
  5.     public Color LineColor { get; set; }
  6.     public enum MarkTypes { Circle, Cross }
  7.     public MarkTypes MarkType { get; set; }
  8.     public DataPoints()
  9.     {
  10.         Loaded += new RoutedEventHandler(DataPoints_Loaded);
  11.     }
  12.     void DataPoints_Loaded(object sender, RoutedEventArgs e)
  13.     {
  14.         MarkStyleBase style;
  15.         if (MarkType == MarkTypes.Circle)
  16.             style = new CircleStyle(LineWidh, LineColor);
  17.         else
  18.             style = new CrossStyle(LineWidh, LineColor);
  19.         foreach (Point p in Points)
  20.         {
  21.             Mark mark = new Mark();
  22.             mark.Style = style.MarkStyle;
  23.             ((Canvas)Parent).Children.Add(mark);
  24.             Canvas.SetLeft(mark, p.X);
  25.             Canvas.SetTop(mark, p.Y);
  26.         }
  27.     }
  28. }

  4つの折れ線を引くつもりで、MainPage.xaml.cs の Loadedイベントで描画してみます。

  1. private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
  2. {
  3.     double y = 30;
  4.     Action<DataPoints.MarkTypes, double, Color> Series = (
  5.         mark_type, line_width, color) =>
  6.     {
  7.         DataPoints dp = new DataPoints();
  8.         dp.Points = new Point[]
  9.         {
  10.             new Point(30,y),
  11.             new Point(130,y),
  12.             new Point(230,y)
  13.         };
  14.         dp.MarkType = mark_type;
  15.         dp.LineWidh = line_width;
  16.         dp.LineColor = color;
  17.         canvas1.Children.Add(dp);
  18.     };
  19.     Series(DataPoints.MarkTypes.Circle, 3, Colors.Red);
  20.     y += 60;
  21.     Series(DataPoints.MarkTypes.Circle, 1, Colors.Blue);
  22.     y += 60;
  23.     Series(DataPoints.MarkTypes.Cross, 2, Colors.Green);
  24.     y += 60;
  25.     Series(DataPoints.MarkTypes.Cross, 4, Colors.Magenta);
  26. }

参照

  問題を整理すると、

  1. XAMLを分割して記述する方法
  2. 記述1つから複数のインスタンスを得る方法

  と、言うことになります。

カスタムコントロールのXAMLとコード
Styleとプロパティ
Styleとバインディング
DataContextとインスタンス
XAML記述とVisualの変換
1つのStyle記述から複数のインスタンス

FrameworkElementFactory(コードでTemplateを作る)


mikeo_410@hotmail.com