WPF のスタイルとテンプレート
Windows Presentation Foundation (WPF) のスタイルとテンプレートは、開発者と設計者が視覚的に説得力のある効果と、製品の一貫した外観を作成するために使用できる一連の機能を指します。 アプリの外観をカスタマイズするときは、アプリ内およびアプリ間での外観の保守と共有を可能にする、強力なスタイルとテンプレートのモデルが必要です。 WPF はそのモデルを提供します。
WPF スタイル モデルのもう 1 つの機能は、表示とロジックの分離です。 設計者は、開発者が C# や Visual Basic を使用してプログラミング ロジックの作業を行っているのと同時に、XAML のみを使用してアプリの外観に関する作業を行うことができます。
この概要では、アプリのスタイルとテンプレートの側面に焦点を当てており、データ バインディングの概念については説明しません。 データ バインディングの詳細については、「データ バインディングの概要」を参照してください。
スタイルとテンプレートの再利用を可能にするものである、リソースについて理解しておくことも重要です。 リソースの詳細については、「XAML Resources」を参照してください。
サンプル
この概要で提供されているサンプル コードは、次の図に示す単純な写真閲覧アプリケーションに基づいています。
この単純な写真のサンプルは、スタイルとテンプレートを使用して、視覚的に説得力のあるユーザー エクスペリエンスを作成します。 このサンプルには、2 つの TextBlock 要素と、画像一覧にバインドされた ListBox コントロールがあります。
完全なサンプルについては、「Introduction to Styling and Templating Sample」を参照してください。
スタイル
Style は、一連のプロパティ値を複数の要素に適用する便利な方法と考えることができます。 FrameworkElement または FrameworkContentElement から派生する任意の要素 (Window や Button など) にスタイルを使用できます。
スタイルを宣言する最も一般的な方法は、XAML ファイルの Resources
セクションでリソースとして行うものです。 スタイルはリソースであるため、すべてのリソースに適用されるものと同じスコープ規則に従います。 つまり、スタイルを宣言する場所は、スタイルを適用できる場所に影響するということです。 たとえば、アプリ定義 XAML ファイルのルート要素でスタイルを宣言すると、そのスタイルは、アプリ内のどこでも使用できます。
たとえば、次の XAML コードは、TextBlock
の 2 つのスタイルを宣言しています。1 つは、すべての TextBlock
要素に自動的に適用され、もう 1 つは明示的に参照する必要があります。
<Window.Resources>
<!-- .... other resources .... -->
<!--A Style that affects all TextBlocks-->
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
<!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
<Style BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock"
x:Key="TitleText">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Foreground">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="#90DDDD" />
<GradientStop Offset="1.0" Color="#5BFFFF" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
上記で宣言されたスタイルの使用例を次に示します。
<StackPanel>
<TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
<TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>
ControlTemplate
WPF では、コントロールの ControlTemplate によって、コントロールの外観が定義されます。 コントロールの構造と外観を変更するには、新しい ControlTemplate を定義し、それをコントロールに割り当てます。 多くの場合、テンプレートによって、独自のカスタム コントロールを作成する必要がない程度に十分な柔軟性を得られます。
各コントロールでは、Control.Template プロパティに既定のテンプレートが割り当てられています。 このテンプレートは、コントロールの視覚表現とコントロールの機能を結びつけます。 テンプレートは XAML で定義するため、コードを記述することなく、コントロールの外観を変更することができます。 各テンプレートは、Button などの特定のコントロール向けに設計されています。
一般に、テンプレートは、XAML ファイルの Resources
セクションでリソースとして宣言します。 すべてのリソースと同様に、スコープ規則が適用されます。
コントロール テンプレートは、スタイルよりもはるかに複雑です。 これは、スタイルが単にプロパティ変更を既存のコントロールに適用するのに対し、コントロール テンプレートはコントロール全体の外観を書き換えるからです。 ただし、コントロールのテンプレートは、Control.Template プロパティを設定して適用されるため、スタイルを使用してテンプレートを定義または設定することができます。
通常、デザイナーでは、既存のテンプレートのコピーを作成して変更することが許可されます。 たとえば、Visual Studio WPF デザイナーで CheckBox
コントロールを選択してから、右クリックし、 [テンプレートの編集]>[コピーの作成] を選択します。 このコマンドは、"テンプレートを定義するスタイル" を生成します。
<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
<Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
<Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid x:Name="markGrid">
<Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
<Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
</Grid>
</Border>
<ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="true">
<Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
<Setter Property="Padding" Value="4,-1,0,0"/>
... content removed to save space ...
テンプレートのコピーを編集することは、テンプレートの動作を理解するための優れた方法です。 新しい空のテンプレートを作成する代わりに、コピーを編集して、視覚表示のいくつかの側面を変更する方が簡単です。
詳細については、「コントロールのためのテンプレートを作成する」を参照してください。
TemplateBinding
前のセクションで定義されたテンプレート リソースで、TemplateBinding マークアップ拡張機能が使用されていることにお気付きかもしれません。 TemplateBinding
は、テンプレート シナリオに合わせて最適化されたバインディングの形態であり、{Binding RelativeSource={RelativeSource TemplatedParent}}
を使用して構築されたバインディングに似ています。 TemplateBinding
は、テンプレートの一部をコントロールのプロパティにバインドするのに便利です。 たとえば、各コントロールには BorderThickness プロパティがあります。 TemplateBinding
を使用して、テンプレート内のどの要素がこのコントロール設定の影響を受けるかを管理します。
ContentControl と ItemsControl
ContentPresenter が ContentControl の ControlTemplate で宣言されている場合、ContentPresenter は自動的に ContentTemplate および Content プロパティにバインドされます。 同様に、ItemsControl の ControlTemplate にある ItemsPresenter は、自動的に ItemTemplate および Items プロパティにバインドされます。
DataTemplate
このサンプル アプリには、写真のリストにバインドされている ListBox コントロールがあります。
<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>
現在、この ListBox は次のようになっています。
ほとんどのコントロールはいくつかの型のコンテンツを持ち、そのコンテンツは多くの場合バインディング先のデータから取得されます。 このサンプルでは、データは写真のリストです。 WPF では、DataTemplate を使用してデータの視覚表現を定義します。 基本的には、DataTemplate に配置するものによって、レンダリングされたアプリでのデータの表示方法が決まります。
サンプル アプリでは、各カスタム Photo
オブジェクトに、画像のファイル パスを指定する文字列型の Source
プロパティがあります。 現在のところ、写真オブジェクトは、ファイルのパスとして表示されます。
public class Photo
{
public Photo(string path)
{
Source = path;
}
public string Source { get; }
public override string ToString() => Source;
}
Public Class Photo
Sub New(ByVal path As String)
Source = path
End Sub
Public ReadOnly Property Source As String
Public Overrides Function ToString() As String
Return Source
End Function
End Class
写真を画像として表示するには、リソースとして DataTemplate を作成します。
<Window.Resources>
<!-- .... other resources .... -->
<!--DataTemplate to display Photos as images
instead of text strings of Paths-->
<DataTemplate DataType="{x:Type local:Photo}">
<Border Margin="3">
<Image Source="{Binding Source}"/>
</Border>
</DataTemplate>
</Window.Resources>
DataType プロパティは Style の TargetType プロパティに似ていることに注意してください。 DataTemplate がリソース セクションにある場合、DataType プロパティを型に指定して x:Key
を省略すると、その型が出現するたびに DataTemplate が適用されます。 常に、DataTemplate に x:Key
を割り当ててから、それを ItemTemplate プロパティや ContentTemplate プロパティなど、DataTemplate 型を取るプロパティの StaticResource
として設定することもできます。
基本的に、上記の例の DataTemplate は、Photo
オブジェクトがあるときは必ず、Border 内の Image として表示するように定義しています。 この DataTemplate を使用すると、アプリは次のようになります。
データ テンプレートのモデルでは、その他の機能を提供します。 たとえば、Menu や TreeView などの HeaderedItemsControl 型を使用して他のコレクションを含むコレクション データを表示する場合は、HierarchicalDataTemplate があります。 もう 1 つのデータ テンプレート機能は DataTemplateSelector です。これを使用すると、カスタム ロジックに基づいて、使用する DataTemplate を選択することができます。 詳細については、「Data Templating Overview」を参照してください。ここでは、さまざまなデータ テンプレート機能を詳しく説明します。
トリガー
トリガーは、プロパティ値が変更されたときまたはイベントが発生したときに、プロパティを設定するかまたはアニメーションなどのアクションを開始します。 Style、ControlTemplate、および DataTemplate はすべて、一連のトリガーを含むことができる Triggers
プロパティ備えています。 トリガーにはいくつかの種類があります。
PropertyTrigger
プロパティの値を設定したりプロパティの値に基づいてアクションを開始したりする Trigger は、プロパティ トリガーと呼ばれます。
プロパティ トリガーを使用する方法を示すために、選択されていない限り、各 ListBoxItem を部分的に透明にすることができます。 次のスタイルは、ListBoxItem の Opacity 値を 0.5
に設定します。 ただし、IsSelected プロパティが true
の場合、Opacity は 1.0
に設定されます。
<Window.Resources>
<!-- .... other resources .... -->
<Style TargetType="ListBoxItem">
<Setter Property="Opacity" Value="0.5" />
<Setter Property="MaxHeight" Value="75" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.Setters>
<Setter Property="Opacity" Value="1.0" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
この例は、Trigger を使用してプロパティ値を設定しますが、Trigger クラスには、トリガーがアクションを実行できるようにする EnterActions および ExitActions プロパティも含まれていることに注意してください。
ListBoxItem の MaxHeight プロパティが 75
に設定されていることに注意してください。 次の図では、3 番目の項目が選択されている項目です。
Eventtrigger とストーリー ボード
別の種類のトリガーは EventTrigger です。これは、イベントの発生に基づいて一連のアクションを開始します。 たとえば、次の EventTrigger オブジェクトは、マウス ポインターが ListBoxItem に入ると、MaxHeight プロパティが 0.2
秒間 90
の値にアニメーション化されることを指定しています。 この項目からマウスを遠ざけると、1
秒間、元の値に戻ります。 MouseLeave アニメーションに対して To 値を指定する必要がないことに注意してください。 これは、アニメーションが元の値を追跡できるからです。
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.Setters>
<Setter Property="Opacity" Value="1.0" />
</Trigger.Setters>
</Trigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetProperty="MaxHeight"
To="90" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:1"
Storyboard.TargetProperty="MaxHeight" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
詳細については、「ストーリーボードの概要」を参照してください。
次の図では、マウスは3 番目の項目を指しています。
MultiTriggers、DataTriggers、および MultiDataTriggers
Trigger と EventTrigger に加えて、他の種類のトリガーがあります。 MultiTrigger を使用すると、複数の条件に基づいてプロパティ値を設定できます。 条件のプロパティがデータバインドされている場合は、DataTrigger と MultiDataTrigger を使用します。
表示状態
コントロールは常に、特定の状態にあります。 たとえば、マウスがコントロールの画面上を移動しているとき、そのコントロールは一般的な MouseOver
の状態であると見なされます。 特定の状態のないコントロールは、一般的な Normal
状態であると見なされます。 状態はグループに分割され、前に述べた状態は CommonStates
という状態グループの一部です。 ほとんどのコントロールには、CommonStates
と FocusStates
の 2 つの状態グループがあります。 コントロールに適用される各状態グループのコントロールは、CommonStates.MouseOver
や FocusStates.Unfocused
など、常に各グループの 1 つの状態になります。 ただし、コントロールは、CommonStates.Normal
や CommonStates.Disabled
など、同じグループ内で 2 つの異なる状態であることはできません。 大部分のコントロールが認識して使用する状態の表を次に示します。
VisualState 名 | VisualStateGroup 名 | 説明 |
---|---|---|
Normal |
CommonStates |
既定の状態です。 |
MouseOver |
CommonStates |
マウス ポインターがコントロール上に配置されます。 |
Pressed |
CommonStates |
コントロールが押されています。 |
Disabled |
CommonStates |
コントロールが無効になっています。 |
Focused |
FocusStates |
コントロールにフォーカスがあります。 |
Unfocused |
FocusStates |
コントロールにフォーカスがありません。 |
コントロール テンプレートのルート要素に System.Windows.VisualStateManager を定義することで、コントロールが特定の状態になったときにアニメーションをトリガーできます。 VisualStateManager
は、監視する VisualStateGroup と VisualState の組み合わせを宣言します。 コントロールが監視している状態になると、VisualStateManager
によって定義されたアニメーションが開始されます。
たとえば、次の XAML コードは、CommonStates.MouseOver
状態を監視して、backgroundElement
という名前の要素の塗りつぶしの色をアニメーション化します。 コントロールが CommonStates.Normal
状態に戻ると、backgroundElement
という名前の要素の塗りつぶしの色が復元されます。
<ControlTemplate x:Key="roundbutton" TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal">
<ColorAnimation Storyboard.TargetName="backgroundElement"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="{TemplateBinding Background}"
Duration="0:0:0.3"/>
</VisualState>
<VisualState Name="MouseOver">
<ColorAnimation Storyboard.TargetName="backgroundElement"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="Yellow"
Duration="0:0:0.3"/>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
ストーリーボードの詳細については、「ストーリーボードの概要」を参照してください。
共有リソースとテーマ
標準的な WPF アプリには、アプリ全体で適用される複数の UI リソースがある場合があります。 ひとまとめにして、この一連のリソースをアプリのテーマと見なすことができます。 WPF では、ResourceDictionary クラスとしてカプセル化されているリソース ディクショナリを使用して UI リソースをテーマとしてパッケージ化することをサポートしています。
WPF のテーマは、要素のビジュアルをカスタマイズするために WPF が公開しているスタイルとテンプレートのメカニズムを使用して定義されます。
WPF のテーマのリソースは、埋め込みリソース ディクショナリに格納されます。 これらのリソース ディクショナリは署名されたアセンブリ内に埋め込む必要があり、コード自体と同じアセンブリまたはサイド バイ サイド アセンブリで埋め込むことができます。 WPF コントロールを含むアセンブリである PresentationFramework.dll の場合、テーマのリソースは、一連の side-by-sede アセンブリに含まれます。
テーマは、要素のスタイルを検索するときに最後に検索する場所になります。 通常、検索を行うには、まず適切なリソースを探索して要素ツリーをたどり、次にアプリのリソース コレクションを調べ、最後にシステムに対してクエリを実行します。 こうすることで、アプリ開発者は、テーマに到達する前に、ツリーまたはアプリのレベルで任意のオブジェクトのスタイルを再定義できます。
リソース ディクショナリは、複数のアプリ間でのテーマの再利用を可能にする個別ファイルとして定義できます。 また、同じ種類のリソースだが値が異なる複数のリソース ディクショナリを定義して、交換できるテーマを作成することもできます。 アプリをスキニングする場合、これらのスタイルまたはその他のリソースをアプリ レベルで再定義することをお勧めします。
スタイルおよびテンプレートを含む一連のリソースをアプリ間で共有するには、XAML ファイルを作成し、shared.xaml
ファイルへの参照を含む ResourceDictionary を定義します。
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
これは、一連のスタイルとブラシのリソースが含まれた ResourceDictionary をそれ自体が定義している shared.xaml
の共有です。これにより、アプリのコントロールに一貫した外観を持たせることができます。
詳細については、「マージされたリソース ディクショナリ」を参照してください。
カスタム コントロールのテーマを作成する場合は、「コントロールの作成の概要」の「テーマ レベルでのリソースの定義」セクションを参照してください。
関連項目
.NET Desktop feedback