mikeo_410


 DataContextとインスタンス

  XAMLでスタイルを定義して、この1つスタイルから異なる形状のコントロールを作ることを考えて見ます。
  XAMLには、サイズと色をバインディングしたEllipseをStyleで定義し、ellipse_style と名前を付けておきます。
  このバインディングデータをLoadedイベント処理で変えながら異なる形状のコントロールを描いてみます。

MainWindow.xaml

  1. <Grid Loaded="Grid_Loaded">
  2.     <StackPanel Name="stackpanel1">
  3.         <StackPanel.Resources>
  4.             <Style x:Key="ellipse_style" TargetType="Control">
  5.                 <Setter Property="Background"
  6.                     Value="{Binding color}"/>
  7.                 <Setter Property="Width" 
  8.                     Value="{Binding width}"/>
  9.                 <Setter Property="Height" 
  10.                     Value="{Binding height}"/>
  11.                 <Setter Property="Template">
  12.                     <Setter.Value>
  13.                         <ControlTemplate>
  14.                             <Ellipse Fill="{TemplateBinding Background}"
  15.                                  Width="{TemplateBinding Width}"
  16.                                  Height="{TemplateBinding Height}"/>
  17.                         </ControlTemplate>
  18.                     </Setter.Value>
  19.                 </Setter>
  20.             </Style>
  21.         </StackPanel.Resources>
  22.     </StackPanel>
  23. </Grid>

MainWindow.xaml.cs

  1. public class C
  2. {
  3.     public Brush color { get; set; }
  4.     public double width { get; set; }
  5.     public double height { get; set; }
  6.     public C(Color color, double width, double height)
  7.     {
  8.         this.color = new SolidColorBrush(color);
  9.         this.width = width;
  10.         this.height = height;
  11.     }
  12. }
  13. public class C1 : C
  14. {
  15.     public C1()
  16.         : base(Colors.Red, 20, 30)
  17.     {
  18.     }
  19. }
  20. public class C2 : C
  21. {
  22.     public C2()
  23.         : base(Colors.Cyan, 50, 30)
  24.     {
  25.     }
  26. }
  27. // GridのLoadedイベント
  28. private void Grid_Loaded(object sender, RoutedEventArgs e)
  29. {
  30.         // 1つ目の楕円(縦長、赤)
  31.         DataContext = new C1();
  32.         Control ctrl1 = new Control();
  33.         ctrl1.Style = (Style)stackpanel1.Resources["ellipse_style"];
  34.         stackpanel1.Children.Add(ctrl1);
  35.         // 2つ目の楕円(横長、シアン)
  36.         DataContext = new C2();
  37.         Control ctrl2 = new Control();
  38.         ctrl2.Style = (Style)stackpanel1.Resources["ellipse_style"];
  39.         stackpanel1.Children.Add(ctrl2);
  40. }

  実行結果は、左図で同じ図形になります。
  DataContextの変更は、それ以前に作成したコントロールのインスタンスにも影響しています。
  これは、2通りの解釈ができます。
  1つは、実行時にもバインディング元の値を参照するようになっていて、その参照にはその都度DataContextが参照される。
  もう1つは、コントロールのStyleプロパティに設定したStyleのインスタンスは同じものなので、Styleが保持している値が変更されると両方変更されることになる。

Styleのコピー

  上の例は、本来異なるStyleのインスタンスをコントロールに割り当てるべきものです。
  Style は XAML記述の方が、エディタの入力支援を受けて効率が良いが、XAMLからは Resources[] に、1対1でしかインスタンスが作れません。

  Styleはコピーするよりも、シリアライズ(XAML記述と Visual の相互変換)を行う方が普通のようですが、バインディングの扱いの理解のためにコピーをして見ます。

  以下のようなコピー関数を作りました。
  肝心のTempleteは、コピーを作ることができず、2つのStyleのインスタンスは、同じのControlTempleteインスタンスを持っています。コピーできたのは、サイズや色のバインディングデータです。Style に加えた Setter のバインディングは、Binding型で、そのまま保持されていました。これを、バインディング先の値に置き換えています。

  1. Style CloneStyle(Style s)
  2. {
  3.     Style d = new Style(s.TargetType);
  4.     foreach (Setter v in s.Setters)
  5.     {
  6.         Debug.WriteLine(v.Property.Name + " " + v.Value);
  7.         if (v.Property.Name == "Template")
  8.         {
  9.             d.Setters.Add(new Setter(v.Property, v.Value));
  10.         }
  11.         else
  12.         {
  13.             if (v.Value.GetType() == typeof(Binding))
  14.             {
  15.                 MemberInfo[] mis = DataContext.GetType().FindMembers(
  16.                     MemberTypes.Property, 
  17.                         BindingFlags.Public | BindingFlags.Instance, 
  18.                         null, null);
  19.                 MemberInfo mi=mis.First(
  20.                     (Func<MemberInfo, bool>)((x) =>
  21.                     {
  22.                         return x.Name == ((Binding)v.Value).Path.Path;
  23.                     }));
  24.                 PropertyInfo pi = (PropertyInfo)mi;
  25.                 object o = pi.GetValue(DataContext, null);
  26.                 d.Setters.Add(new Setter(v.Property, o));
  27.             }
  28.             else
  29.             {
  30.                 d.Setters.Add(new Setter(v.Property, v.Value));
  31.             }
  32.         }
  33.     }
  34.     return d;
  35. }

  コントロールを生成する箇所をこの関数を使うように修正しました。

  1. // 1つ目の楕円(縦長、赤)
  2. DataContext = new C1();
  3. Control ctrl1 = new Control();
  4. ctrl1.Style = CloneStyle((Style)stackpanel1.Resources["ellipse_style"]);
  5. stackpanel1.Children.Add(ctrl1);

  結果は、図のようになります。
  最初の例で、2つの図形が同じになるのは、Styleのインスタンスが同じだからと言うよりは、バインディングしているデータが同じだからと言うことでした。

  このことから、XAMLファイルで記述した1つのStyleを、コードでResources[]から取り出して使う方法でも、複数の外観を持たせることは可能だとわかりました。
  条件は、Template が共通に使えるなら、と言うことになります。

参照

  問題を整理すると、

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

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

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

FrameworkElementFactory(コードでTemplateを作る)


mikeo_410@hotmail.com