スタイル
最終更新:
atachi
WPFのスタイルは、スタイルという名前ですがデザインやカラー設定を行うためだけの機能ではありません。 スタイルを定義するにはStyle要素を使用します。Style要素内ではコンポーネントのプロパティ値の設定やデータのプロパティ値の設定ができます。
データテンプレートでは再利用可能なデータバインディングを持ったスタイルを定義します。
コントロールテンプレートでは既存のコントロールのUIデザインを定義できます。
スタイルの使用方法
スタイルはXAMLのResourcesに記述します。
Style.TargetType属性の指定し、XAMLデザイナがSetter要素のPropertyの値を検証するためにも使用します。
Setter要素の値はValue属性に記述することもできますし、次のようにプロパティ要素として記述することもできます。
<Setter Property="PropertyA"> <Setter.Value> プロパティ要素 </Setter.Value> </Setter>
次のコードはXAML内のすべてのTextBlock要素にスタイルが適応されます。
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily">
<Setter.Value>
Comic Sans MS
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TextBlock Text="ようこそ。" />
<TextBlock Text="今日は12月8日です。" />
</Grid>
スタイルだけを先に定義し、各VisualObjectからスタイルを参照したい場合、Style.TargetStyle属性を使用せずにx:Key属性を使ってスタイルの定義だけを行います。
<Window.Resources>
<Style x:Key="MyStyle">
<Setter Property="TextBlock.HorizontalAlignment" Value="Center" />
<Setter Property="TextBlock.FontSize" Value="14" />
<Setter Property="TextBlock.FontFamily" Value="Comic Sans MS" />
</Style>
</Window.Resources>
特定の要素にスタイルを適応
Style要素の定義時にx:Key要素でスタイルに名前をつけた場合、スタイルが適応されるには明示的にスタイル名を指定する必要があります。
次の例では2つのTextBlockのうち、1番目のTextBlockだけにスタイルが適応されます。
<Window.Resources>
<Style x:Key="MyStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily" Value="Comic Sans MS" />
</Style>
</Window.Resources>
<Grid>
<TextBlock Text="ようこそ。" Style="{StaticResource MyStyle}" />
<TextBlock Text="今日は12月8日です。" />
</Grid>
スタイルを継承
Style.BaseOn属性を使用してスタイルを継承し、そこから設定済みプロパティの再設定やプロパティの追加が行えます。
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="20"/>
</Style>
<Style x:Key="MyColorStyle" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock} }">
<Setter Property="Foreground" Value="#FFC13B3B" />
</Style>
</Window.Resources>
<Grid>
<StackPanel Height="100" HorizontalAlignment="Left" Name="stackPanel1" VerticalAlignment="Top" Width="200">
<TextBlock Height="23" Name="textBlock2" Text="TextBlock" />
<TextBlock Height="70" Name="textBlock1" Text="よだー" Style="{StaticResource MyColorStyle}"/>
</StackPanel>
</Grid>
MyColorStyleは既存のTextBlockのスタイルを継承しています。
テンプレート
- データテンプレート
- コントロールテンプレート
それぞれUIの描画スタイルを設定する手法で、再利用可能なことからテンプレートと呼ばれている。
データテンプレートとは主にコレクションをアイテムとして表示するコントロール(ListBox、ListView、TreeView、DataGrid、その他)で、そのアイテムを描画するためのUIを定義したものです。
コントロールテンプレートとはUIの描画そのものを再定義するものです。Buttonコントロールは1つのコントロールに見えますが内部ではGridやTextBlockを組み合わせて1つのコントロールとして描画しています。
データテンプレート
VisualObjectを持つスタイルをデータテンプレートと呼びます。
データテンプレートではデータを表示する為のオブジェクト要素構造をテンプレート化し、UIデザインとデータの両方の再利用を実現できます。
テンプレート化したオブジェクト要素構造は、バインディングによって動的に表示内容を変更するように設計します。
次のサンプルでは、ListBoxに設定したデータソースの要素を描画する際に、各要素を描画するためのテンプレートを定義しています。
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList} }">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
データテンプレートはリソースとして定義できます。次のように予めリソースとして定義しておきx:Key属性で指定した名前を定義しておけば、データテンプレートの再利用が可能です。
ListBox.ItemTemplateへはStaticResourceを使用してリソースを呼び出します。
<Window
xmlns:local="clr-namespace:my" >
<Window.Resources>
<ObjectDataProvider x:Key="myTodoList" ObjectType="{x:Type local:Tasks}"/>
<DataTemplate x:Key="myTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<!-- リソースに定義した「myTemplate」をデータテンプレートに使用する -->
<ListBox Width="400"
Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList} }"
ItemTemplate="{StaticResource myTemplate" />
</Window>
namespace my {
public class Tasks : ObservableCollection<Task> {
public Tasks() {
this.Add(new Task{TaskName="テレビを見る", Description="水戸黄門", Priority=3});
this.Add(new Task{TaskName="電話", Description="佐藤さんに電話", Priority=10});
this.Add(new Task{TaskName="勉強", Description="C#の勉強に明け暮れる", Priority=5});
}
}
public class Task {
public string TaskName {
get;
set;
}
puiblic string Description {
get;
set;
}
public int Priority {
get;
set;
}
}
}
データテンプレートをクラスに適応させる
DataTemplate.DateType を使用すると特定のオブジェクトすべてにデータテンプレートを適用することができます。
この仕組みは
非常に重要
で、任意のC#コードで記述されたクラスに対してXAMLによるUIのデザインを設定できます。
次のサンプルではListBox.ItemTemplateへデータテンプレートの設定を行っていません。しかし、正常に動作します。
これはListBoxがTaskオブジェクトを表示する際に、DataTemplateによって設定されたデータテンプレートが使用されている為です。
<Window
xmlns:local="clr-namespace:my" >
<Window.Resources>
<ObjectDataProvider x:Key="myTodoList" ObjectType="{x:Type local:Tasks}"/>
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ListBox Width="400"
Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList} }" />
</Window>
テンプレート内にリソースを適応させる
ComboBoxなどアイテムソースを受け取るコントロールでは、表示させるアイテムをItemsSourceプロパティなどに設定します。
通常はItemsSourceプロパティにはバインディングやリソースを行って項目を指定します。
しかし、データテンプレート内でバインディングを指定する場合には注意が必要です。
バインディングを使用する場合、バインドソースはデータテンプレートに与えられたアイテムになります。(ItemsSourceに設定されたコレクションの各要素がバインドソースとなる)
データテンプレート内ですべてのComboBoxに同じ選択肢を表示したい場合には StaticResourceマークアップ拡張 を使用します。
<Window.Resources>
<src:MyList x:Key="myList" />
<col:ArrayList x:Key="MyDataSource">
<sys:DateTime>1/2/2003 5:00:00</sys:DateTime>
<sys:DateTime>4/5/2006 13:13:13</sys:DateTime>
<sys:DateTime>7/8/2009 23:59:59</sys:DateTime>
</col:ArrayList>
</Window.Resources>
<Grid>
<ListView ItemsSource="{StaticResource MyDataSource}" >
<ListView.View>
<GridView>
<GridViewColumn Header="月" DisplayMemberBinding="{Binding Month}" />
<GridViewColumn Header="日" DisplayMemberBinding="{Binding Day}" />
<GridViewColumn Header="日付" DisplayMemberBinding="{Binding DayOfWeek}"/>
<GridViewColumn>
<GridViewColumn.Header>
<TextBlock Text="何か選択" />
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="comboBox1" Width="100" ItemsSource="{StaticResource ResourceKey=myList}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridView>
</ListView.View>
</ListView>
</Grid>
class MyList : List<string> {
public MyList() {
this.Add("日本");
this.Add("韓国");
this.Add("北朝鮮");
this.Add("中国");
this.Add("台湾");
this.Add("フィリピン");
this.Add("タイ");
this.Add("ベトナム");
}
}
DataTemplateとバインドソース
<ContentPresenter Name="stp1"
Content="WPFは複雑"
Grid.Row="1" Grid.Column="1" Height="22" Margin="9,0,0,0">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
親要素のContentが参照される
この例では、TextBlockは「WPFは複雑」と表示されます。
理由は純粋に、ContentプロパティがInhert属性のプロパティなので、ContentPresenterがテンプレートを使ってビジュアルツリーを構築する際に、TextBlock.ContentもContentPresenter.Contentを参照します。
コントロールテンプレート
コントロールテンプレートではコントロールの構造と外観を定義します。
スタイルとしてコントロールテンプレートを適応することで、コントロールの構造や外観を再定義することができます。
コントロールテンプレートはControlクラスを継承した多くのコントロールに対して適応できます。
コントロールテンプレートを適応するというのは、そのコントロールのビジュアルツリーを書き換えることを意味します。
ただし、コントロールのビジュアルツリーの一部だけを書き換えることはできません。
カスタムコントロールテンプレート例
トリガー
プロパティの値を判断し、動的に任意のプロパティ値を設定することができる。
if構文と違い、判断するデータソースや設定可能なデータソースは限られている。
データテンプレートで使用する場合は、そのデータがデータソースとなる。(ItemsSource属性を持つComboBoxやListBoxの場合、各要素をデータテンプレートを使って表示する際の各要素がデータソースとなる)
よって、プログラマが自由なデータソースを指定し、その値を動的に評価して処理を分岐するといったif構文のような処理は行えない。