1つのStyle記述から複数のインスタンス
System.Windows.Controls.DataVisualization.Charting のChartで折れ線を書く場合、折れ線上に書かれるマークをStyleで設定します。
マークの形状は少数ですが、サイズと色はいろいろです。サイズは、基本的に線幅から計算します。色は、線に指定した色を使います。
したがって、すべてをあらかじめXAMLで記述して置くことはできません。
形をXAMLで記述して置き、サイズや色を実行時に変えたStyleのインスタンスを作る方法が必要です。
XAMLで記述したStyleは、実行時にResources[]から取り出せますが、これはStyleのインスタンスで、参照のたびにインスタンスが作られるわけではありません。
1つのインスタンスでは、前に引いた線のマークまで変わってしまします。
また、一度FrameworkElementのインスタンスのStyleプロパティに使うとシールされて変更できなくなります。
XAML記述を文字列としてビルドする
以下のような関数で、XAML文字列を作成します。
- static string xaml_start =
- @"<ControlTemplate " +
- "xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " +
- "xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>" +
- "<Grid>";
- static string xaml_end =
- "</Grid>" +
- "</ControlTemplate>";
- //---------------------------------------------------------------------------
- // 丸いマーク用のXAML
- //---------------------------------------------------------------------------
- static string circle_xaml(double width, double height, Color color, bool fill)
- {
- string s = xaml_start;
- s += @"<Ellipse Width='" + width + @"' "
- + @"Height='" + height + @"' "
- + @"Stroke='" + color + @"' "
- + @"StrokeThickness='1' ";
- if (fill)
- {
- s += @"Fill='" + color.ToString() + @"' ";
- }
- s += @"/>" + xaml_end;
- return s;
- }
これを、読み込んで毎回新しいStyleインスタンスを作ります。
- static ControlTemplate LoadXaml(string s)
- {
- #if SILVERLIGHT
- return (ControlTemplate)XamlReader.Load(s);
- #else
- MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(s));
- return (ControlTemplate)XamlReader.Load(stream);
- #endif
- }
この方法は、XAMLの編集が、エディタの支援を受けられないので少量の場合向きです。
Application.LoadComponent を使う
実行時にXAML記述を読み込む方法ですが、実行形式以外にファイルが必要なのは不便なので Visual Studio が実行形式に組み込む方法が必要です。
また、エディタの入力支援がないとほとんど記述できません。
この方法は、ビハインドコードを伴わない単独のXAMLファイルをプロジェクトに追加します。
Visual Studio は、これを実行形式に組み込んでくれます。
これを、実行時にApplication.LoadComponent()で読み込むことができます。
この機能は、FrameworkElementを作るもので、XAML記述のルートは、FrameworkElementである必要があります。
ここでは、ContentControlにして見ました。
その、ResourcesにStyleを記述します。
Styleには、形状を決めるTempleteプロパティのみを記述します。
サイズやカラーは、TemplateBindingで設定します。
丸のマークを定義したXAMLファイルは以下のようです。
- <ContentControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:u="clr-namespace:SilverlightApplication1">
- <ContentControl.Resources>
- <Style x:Key="mark" TargetType="u:Mark">
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate>
- <Ellipse Fill="{TemplateBinding Background}"
- Width="{TemplateBinding Width}"
- Height="{TemplateBinding Height}"/>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- </ContentControl.Resources>
- </ContentControl>
この記述から、毎回新しいStyleを返すために以下のようにしました。
- // マークのベースクラス
- public class MarkStyleBase
- {
- // コンストラクタ
- public MarkStyleBase(string xaml_file_name, double width, Color color)
- {
- // 同じ形のマークは1回だけロードするように
- if (cc == null)
- {
- cc = new ContentControl();
- Uri resourceLocater
- = new Uri("/SilverlightApplication1;component/" + xaml_file_name,
- System.UriKind.Relative);
- Application.LoadComponent(cc, resourceLocater);
- Debug.WriteLine("MarkStyleBase()");
- }
- // リソースからTemplateの定義を取り出して、新しいStyleを作る
- Style style = (Style)cc.Resources["mark"];
- MarkStyle = new Style(style.TargetType);
- MarkStyle.Setters.Add(new Setter(
- ((Setter)style.Setters[0]).Property,
- ((Setter)style.Setters[0]).Value));
- // 色を設定
- MarkStyle.Setters.Add(new Setter(
- ContentControl.BackgroundProperty, new SolidColorBrush(color)));
- // サイズを設定
- MarkStyle.Setters.Add(new Setter(
- ContentControl.WidthProperty, width * 10));
- MarkStyle.Setters.Add(new Setter(
- ContentControl.HeightProperty, width * 10));
- }
- virtual protected ContentControl cc { get; set; }
- public Style MarkStyle { get; set; }
- }
- // 丸いマーク
- public class CircleStyle : MarkStyleBase
- {
- public CircleStyle(double line_width, Color line_color)
- : base("CircleMark.xaml", line_width, line_color)
- {
- }
- static ContentControl _cc = null; // スタティック
- override protected ContentControl cc
- { get { return _cc; } set { _cc = value; } }
- }
- // Xマーク
- public class CrossStyle : MarkStyleBase
- {
- public CrossStyle(double line_width, Color line_color)
- : base("CrossMark.xaml", line_width, line_color)
- {
- }
- static ContentControl _cc = null; // スタティック
- override protected ContentControl cc
- { get { return _cc; } set { _cc = value; } }
- }
CircleStyleクラス、CrossStyleクラスがあり、これをコンストラクトし、MarkStyleプロパティに設定されたStyleを使います。
XAMLを読み込むことで作られるContentControlのインスタンスは、スタティックな変数に割り当て読み込むのは1回にします。
この中のResource[]のStyleを使いますが、Templateプロパティだけを取り出して使います。
Templateプロパティにあるオブジェクトは、すべて同じインスタンスがセットされることになります。
Styleは、CircleStyleクラス、CrossStyleクラスが new されるたびに新しいインスタンスを返しますが、そのTemplate部分は同じオブジェクトです。
サイズと色に関する設定は、Styleの個々のインスタンスに固有です。
このスタイルを割り当てる Markクラスは以下のようです。
- public class Mark : Control
- {
- public Mark()
- {
- }
- }
折れ線グラフの1つのデータ列(1つの折れ線)を表すクラスのつもりで以下のクラスを用意しました。
- public class DataPoints : FrameworkElement
- {
- public Point[] Points { get; set; }
- public double LineWidh { get; set; }
- public Color LineColor { get; set; }
- public enum MarkTypes { Circle, Cross }
- public MarkTypes MarkType { get; set; }
- public DataPoints()
- {
- Loaded += new RoutedEventHandler(DataPoints_Loaded);
- }
- void DataPoints_Loaded(object sender, RoutedEventArgs e)
- {
- MarkStyleBase style;
- if (MarkType == MarkTypes.Circle)
- style = new CircleStyle(LineWidh, LineColor);
- else
- style = new CrossStyle(LineWidh, LineColor);
- foreach (Point p in Points)
- {
- Mark mark = new Mark();
- mark.Style = style.MarkStyle;
- ((Canvas)Parent).Children.Add(mark);
- Canvas.SetLeft(mark, p.X);
- Canvas.SetTop(mark, p.Y);
- }
- }
- }
4つの折れ線を引くつもりで、MainPage.xaml.cs の Loadedイベントで描画してみます。
- private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
- {
- double y = 30;
- Action<DataPoints.MarkTypes, double, Color> Series = (
- mark_type, line_width, color) =>
- {
- DataPoints dp = new DataPoints();
- dp.Points = new Point[]
- {
- new Point(30,y),
- new Point(130,y),
- new Point(230,y)
- };
- dp.MarkType = mark_type;
- dp.LineWidh = line_width;
- dp.LineColor = color;
- canvas1.Children.Add(dp);
- };
- Series(DataPoints.MarkTypes.Circle, 3, Colors.Red);
- y += 60;
- Series(DataPoints.MarkTypes.Circle, 1, Colors.Blue);
- y += 60;
- Series(DataPoints.MarkTypes.Cross, 2, Colors.Green);
- y += 60;
- Series(DataPoints.MarkTypes.Cross, 4, Colors.Magenta);
- }
参照
問題を整理すると、
-
XAMLを分割して記述する方法
-
記述1つから複数のインスタンスを得る方法
と、言うことになります。
「カスタムコントロールのXAMLとコード」
「Styleとプロパティ」
「Styleとバインディング」
「DataContextとインスタンス」
「XAML記述とVisualの変換」
「1つのStyle記述から複数のインスタンス」
「FrameworkElementFactory(コードでTemplateを作る)」
|