mikeo_410


 Styleとプロパティ

Styleとプロパティ

  XAMLで記述する内容は、タグとプロパティでできています。
  タグは、FrameworkElementを継承したクラスです。
  プロパティは、そのクラスが公開しているプロパティです。
  このプロパティは、Styleでも設定できるようになっています。

  1. <TextBlock Text="ABC" Foreground="Red">
  2.     <TextBlock.Style>
  3.         <Style TargetType="TextBlock">
  4.             <Setter Property="Foreground" Value="Green"/>
  5.         </Style>
  6.     </TextBlock.Style>
  7. </TextBlock>

  この場合、実際に表示される文字列の色は赤になります。
  Styleとプロパティの関係を見るために、簡単なクラスを作ってみます。
  Class1クラスに、2つのプロパティ Property1、Property2 を作り、それを以下のXAMLで使用します。

  1. <Grid Loaded="Grid_Loaded" 
  2.       xmlns:u="clr-namespace:WpfApplication1">
  3.     <u:Class1 Property1="1 2 3">
  4.         <u:Class1.Style>
  5.             <Style TargetType="u:Class1">
  6.                 <Setter Property="Property2" Value="9 8 7"/>
  7.             </Style>
  8.         </u:Class1.Style>
  9.     </u:Class1>
  10. </Grid>

  Property1は、プロパティとして値を代入し、Property2には、Setterで値をセットしています。
  SettertでProperty1を設定するのは、実行時にエラーとなります。

  1. public class Class1 : FrameworkElement
  2. {
  3.     public DoubleCollection Property1 { get; set; }
  4.     public DoubleCollection Property2 { get; set; }
  5.     // このプロパティを依存関係プロパティに登録
  6.     public static readonly DependencyProperty Property2Property =
  7.         DependencyProperty.Register(
  8.             "Property2",
  9.             typeof(DoubleCollection),
  10.             typeof(Class1),
  11.             new PropertyMetadata(Property2PropertyChanged));
  12.     // 依存関係プロパティの変更イベント(スタティック)
  13.     static void Property2PropertyChanged(DependencyObject d,
  14.                             DependencyPropertyChangedEventArgs e)
  15.     {
  16.         ((Class1)d).Property2 =
  17.             (DoubleCollection)e.NewValue;
  18.     }
  19.     public Class1()
  20.     {
  21.         Loaded += new RoutedEventHandler(Class1_Loaded);
  22.     }
  23.     void Class1_Loaded(object sender, RoutedEventArgs e)
  24.     {
  25.         Debug.WriteLine(Property1);
  26.         Debug.WriteLine(Property2);
  27.     }
  28. }

  Setterで扱うのは、依存関係プロパティであることが必要なようです。

  実際の実行順は、プロパティの設定、Setterによる設定の順に行われます。
  これは、最初の TextBlockの Foreground の例の結果と矛盾します。
  本当のところはわかりませんが、初期化中にプロパティで設定された値は、初期化の間は変更されないように動作するものと解釈しておきます。

Styleの実態1

  前述のClass1に対するStyleの設定は、Class1のStyleプロパティを設定することは明白です。
  以下の場合は、どうでしょうか。

  1. <Grid Loaded="Grid_Loaded" 
  2.       xmlns:u="clr-namespace:WpfApplication1">
  3.     <Grid.Resources>
  4.         <Style TargetType="u:Class1">
  5.             <Setter Property="Property2" Value="9 8 7"/>
  6.         </Style>
  7.     </Grid.Resources>
  8.     <u:Class1 x:Name="C1" Property2="1 2 3"/>
  9.     <u:Class1 x:Name="C2"/>
  10.     <u:Class1 x:Name="C3">
  11.         <u:Class1.Style>
  12.             <Style TargetType="u:Class1">
  13.                 <Setter Property="Property2" Value="7 7 7"/>
  14.             </Style>
  15.         </u:Class1.Style>
  16.     </u:Class1>
  17. </Grid>

  この記述によって、3つのClass1型のインスタンス C1、C2、C3 が生成されます。
  Class1のLoadedイベント処理を以下のように修正して、どのインスタンスか区別がつくようにします。

  1. void Class1_Loaded(object sender, RoutedEventArgs e)
  2. {
  3.     Debug.WriteLine(Name + " " + Property2);
  4. }

  MainWindowの Loadedイベント処理に以下の行を入れて、インスタンスの同一性を確認します。

  1. Debug.WriteLine(ReferenceEquals(C1.Style, C2.Style));
  2. Debug.WriteLine(ReferenceEquals(C1.Style, C3.Style));

  結果は、以下のようになりました。

  1. True
  2. False
  3. C1 1 2 3
  4. C2 9 8 7
  5. C3 7 7 7
  1. Resourceで定義したStyleは1つのインスタンスが生成され、Style宣言されていないClass1インスタンスのStyleに設定される
  2. 実際の設定値は、Styleよりプロパティの設定が優先される

Styleの実態2

  「Styleの実態1」で確認したことを、コードで再確認します。

  1. <StackPanel Name="stackpanel1">
  2.     <StackPanel.Resources>
  3.         <Style x:Key="ellipse_style1" TargetType="Control">
  4.             <Setter Property="Background" Value="Cyan"/>
  5.             <Setter Property="Template">
  6.                 <Setter.Value>
  7.                     <ControlTemplate>
  8.                         <Ellipse Fill="{TemplateBinding Background}"/>
  9.                     </ControlTemplate>
  10.                 </Setter.Value>
  11.             </Setter>
  12.         </Style>
  13.     </StackPanel.Resources>
  14. </StackPanel>

  この Style を使って、直径の違う円を2つ描くことを考えます。

  1. Control c = new Control();
  2. Style style = (Style)stackpanel1.Resources["ellipse_style1"];
  3. style.Setters.Add(new Setter(WidthProperty, 20.0));
  4. style.Setters.Add(new Setter(HeightProperty, 20.0));
  5. c.Style = style;
  6. stackpanel1.Children.Add(c);
  7. Control c2 = new Control();
  8. Style style2 = (Style)stackpanel1.Resources["ellipse_style1"];
  9. //style2.Setters.Add(new Setter(WidthProperty, 40.0));
  10. //style2.Setters.Add(new Setter(HeightProperty, 40.0));
  11. c2.Style = style2;
  12. stackpanel1.Children.Add(c2);

  実際には、2回目のサイズ設定でエラーが起きます。これをコメントにすると、同じサイズの円が2つ描かれることになります。
  XAML記述からは、Style のインスタンスが、stackpanel1.Resources["ellipse_style1"] で参照される場所に作られます。
  Width、Height の設定は、この Setters に、WidthProperty、HedthProperty を加える操作になります。
  このスタイルを、1度FrameworkElementのStyleに設定すると、シールされ変更できなくなります。
  このため、2度目のサイズ設定が失敗します。

  1. FrameworkElement の Style プロパティに設定されている、スタイルのインスタンスは、シールされていて変更できない。
    FrameworkElement の Style プロパティは、変更できる。スタイルを変えるには、新しいスタイルをプロパティに代入する。
  2. XAML記述からResourcesに作られるのは、そのインスタンスそのもの。XAML記述のようなインスタンスを作る元ではない。
  3. 例のように、複数のインスタンスを生成する必要がある場合には、クローン、ディープコピー、シリアライズと言った方法が考えられるが有効な方法は見つからない。
  4. おそらく、XAML記述からFrameworkElementをその都度生成するのが正攻法。

  WPFだけにある機能ですが、FrameworkElementFactoryを使う解決方法もあります。「FrameworkElementFactory(コードでTemplateを作る)

参照

  問題を整理すると、

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

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

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

FrameworkElementFactory(コードでTemplateを作る)



mikeo_410@hotmail.com