スタイルとテンプレート (WPF .NET)

Windows Presentation Foundation (WPF) のスタイルとテンプレートは、開発者と設計者が視覚的に説得力のある効果と、製品の一貫した外観を作成するために使用できる一連の機能を指します。 アプリの外観をカスタマイズするときは、アプリ内およびアプリ間での外観の保守と共有を可能にする、強力なスタイルとテンプレートのモデルが必要です。 WPF はそのモデルを提供します。

WPF スタイル モデルのもう 1 つの機能は、表示とロジックの分離です。 設計者は、開発者が C# や Visual Basic を使用してプログラミング ロジックの作業を行っているのと同時に、XAML のみを使用してアプリの外観に関する作業を行うことができます。

この概要では、アプリのスタイルとテンプレートの側面に焦点を当てており、データ バインディングの概念については説明しません。 データ バインディングの詳細については、「データ バインディングの概要」を参照してください。

スタイルとテンプレートの再利用を可能にするものである、リソースについて理解しておくことも重要です。 リソースの詳細については、XAML リソースの概要に関するページを参照してください。

重要

.NET 6 と .NET 5 (.NET Core 3.1 を含む) 用のデスクトップ ガイド ドキュメントは作成中です。

サンプル

この概要で提供されているサンプル コードは、次の図に示す単純な写真閲覧アプリケーションに基づいています。

Styled ListView

この単純な写真のサンプルは、スタイルとテンプレートを使用して、視覚的に説得力のあるユーザー エクスペリエンスを作成します。 このサンプルには、2 つの TextBlock 要素と、画像一覧にバインドされた ListBox コントロールがあります。

完全なサンプルについては、「Introduction to Styling and Templating Sample」を参照してください。

スタイル

Style は、一連のプロパティ値を複数の要素に適用する便利な方法と考えることができます。 FrameworkElement または FrameworkContentElement から派生する任意の要素 (WindowButton など) にスタイルを使用できます。

スタイルを宣言する最も一般的な方法は、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>

Styled textblocks

詳細については、コントロールのスタイルの作成に関するページを参照してください。

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

ContentPresenterContentControlControlTemplate で宣言されている場合、ContentPresenter は自動的に ContentTemplate および Content プロパティにバインドされます。 同様に、ItemsControlControlTemplate にある ItemsPresenter は、自動的に ItemTemplate および Items プロパティにバインドされます。

DataTemplate

このサンプル アプリには、写真のリストにバインドされている ListBox コントロールがあります。

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

現在、この ListBox は次のようになっています。

ListBox before applying template

ほとんどのコントロールはいくつかの型のコンテンツを持ち、そのコンテンツは多くの場合バインディング先のデータから取得されます。 このサンプルでは、データは写真のリストです。 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 プロパティは StyleTargetType プロパティに似ていることに注意してください。 DataTemplate がリソース セクションにある場合、DataType プロパティを型に指定して x:Key を省略すると、その型が出現するたびに DataTemplate が適用されます。 常に、DataTemplatex:Key を割り当ててから、それを ItemTemplate プロパティや ContentTemplate プロパティなど、DataTemplate 型を取るプロパティの StaticResource として設定することもできます。

基本的に、上記の例の DataTemplate は、Photo オブジェクトがあるときは必ず、Border 内の Image として表示するように定義しています。 この DataTemplate を使用すると、アプリは次のようになります。

Photo image

データ テンプレートのモデルでは、その他の機能を提供します。 たとえば、MenuTreeView などの HeaderedItemsControl 型を使用して他のコレクションを含むコレクション データを表示する場合は、HierarchicalDataTemplate があります。 もう 1 つのデータ テンプレート機能は DataTemplateSelector です。これを使用すると、カスタム ロジックに基づいて、使用する DataTemplate を選択することができます。 詳細については、「Data Templating Overview」を参照してください。ここでは、さまざまなデータ テンプレート機能を詳しく説明します。

トリガー

トリガーは、プロパティ値が変更されたときまたはイベントが発生したときに、プロパティを設定するかまたはアニメーションなどのアクションを開始します。 StyleControlTemplate、および DataTemplate はすべて、一連のトリガーを含むことができる Triggers プロパティ備えています。 トリガーにはいくつかの種類があります。

PropertyTrigger

プロパティの値を設定したりプロパティの値に基づいてアクションを開始したりする Trigger は、プロパティ トリガーと呼ばれます。

プロパティ トリガーを使用する方法を示すために、選択されていない限り、各 ListBoxItem を部分的に透明にすることができます。 次のスタイルは、ListBoxItemOpacity 値を 0.5 に設定します。 ただし、IsSelected プロパティが true の場合、Opacity1.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 プロパティも含まれていることに注意してください。

ListBoxItemMaxHeight プロパティが 75 に設定されていることに注意してください。 次の図では、3 番目の項目が選択されている項目です。

Styled ListView

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 番目の項目を指しています。

Styling sample screenshot

MultiTriggers、DataTriggers、および MultiDataTriggers

TriggerEventTrigger に加えて、他の種類のトリガーがあります。 MultiTrigger を使用すると、複数の条件に基づいてプロパティ値を設定できます。 条件のプロパティがデータバインドされている場合は、DataTriggerMultiDataTrigger を使用します。

表示状態

コントロールは常に、特定の状態にあります。 たとえば、マウスがコントロールの画面上を移動しているとき、そのコントロールは一般的な MouseOver の状態であると見なされます。 特定の状態のないコントロールは、一般的な Normal 状態であると見なされます。 状態はグループに分割され、前に述べた状態は CommonStates という状態グループの一部です。 ほとんどのコントロールには、CommonStatesFocusStates の 2 つの状態グループがあります。 コントロールに適用される各状態グループのコントロールは、CommonStates.MouseOverFocusStates.Unfocused など、常に各グループの 1 つの状態になります。 ただし、コントロールは、CommonStates.NormalCommonStates.Disabled など、同じグループ内で 2 つの異なる状態であることはできません。 大部分のコントロールが認識して使用する状態の表を次に示します。

VisualState 名 VisualStateGroup 名 説明
標準 CommonStates 既定の状態です。
MouseOver CommonStates マウス ポインターがコントロール上に配置されています。
押されている CommonStates コントロールが押されています。
無効 CommonStates コントロールが無効になっています。
フォーカスされている FocusStates コントロールにフォーカスがあります。
フォーカスされていない FocusStates コントロールにフォーカスがありません。

コントロール テンプレートのルート要素に System.Windows.VisualStateManager を定義することで、コントロールが特定の状態になったときにアニメーションをトリガーできます。 VisualStateManager は、監視する VisualStateGroupVisualState の組み合わせを宣言します。 コントロールが監視している状態になると、VisaulStateManager によって定義されたアニメーションが開始されます。

たとえば、次の 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 の共有です。これにより、アプリのコントロールに一貫した外観を持たせることができます。

詳細については、「マージされたリソース ディクショナリ」を参照してください。

カスタム コントロールのテーマを作成する場合は、「コントロールの作成の概要」の「テーマ レベルでのリソースの定義」セクションを参照してください。

関連項目