XAML を使用してアプリをスタイル設定する

.NET Multi-Platform App UI (.NET MAUI) アプリには、多くの場合、外観が同じ複数のコントロールが含まれています。 たとえば、1 つのアプリに、同じフォント オプションとレイアウト オプションを持つ複数の Label インスタンスがある場合があります。

<Label Text="These labels"
       HorizontalOptions="Center"
       VerticalOptions="Center"
       FontSize="18" />
<Label Text="are not"
       HorizontalOptions="Center"
       VerticalOptions="Center"
       FontSize="18" />
<Label Text="using styles"
       HorizontalOptions="Center"
       VerticalOptions="Center"
       FontSize="18" />

この例では、各 Label オブジェクトには、Label で表示されるテキストの外観を制御するための同じプロパティ値があります。 しかし、各コントロールの外観を設定することは、繰り返しが多く、エラーが発生しやすくなります。 代わりに、外観を定義するスタイルを作成でき、必要なコントロールに適用できます。

スタイルの概要

Style クラスを使用してアプリをスタイル設定し、プロパティ値のコレクションを 1 つのオブジェクトにグループ化して、複数のビジュアル要素に適用できるようにします。 これにより、繰り返されるマークアップを減らして、アプリの外観をより簡単に変更できるようにします。

スタイルは主に XAML ベースのアプリ用に設計されていますが、C# で作成することもできます。

  • XAML で作成された Style オブジェクトは、一般的に ResourceDictionary で定義されます。これは、コントロール、ページの Resources コレクション、またはアプリの Resources コレクションに割り当てられます。
  • C# で作成された Style オブジェクトは、通常、ページのクラス、またはグローバルにアクセスできるクラスで定義されます。

Style を定義する場所の選択は、それを使用できる場所に影響します。

  • コントロール レベルで定義された Style インスタンスは、ページとその子にのみ適用できます。
  • ページ レベルで定義された Style インスタンスは、ページとその子にのみ適用できます。
  • アプリ レベルで定義された Style インスタンスは、アプリ全体に適用できます。

Style オブジェクトには、1 つ以上の Setter オブジェクトのコレクションが含まれています。Setter にはそれぞれ PropertyValue があります。 Property は、スタイルが適用される要素のバインド可能なプロパティの名前で、Value はプロパティに適用される値です。

Style オブジェクトは、明示的または暗黙的に指定できます。

  • 明示的なStyle オブジェクトは、TargetTypex:Key の値を指定し、ターゲット要素の Style プロパティを x:Key 参照に設定することによって定義されます。 詳細については、「Explicit styles」をご覧ください。
  • 暗黙的Style オブジェクトは、TargetType のみを指定することで定義されます。 その後、Style オブジェクトはその型のすべての要素に自動的に適用されます。 ただし、TargetType のサブクラスには Style は自動的には適用されません。 詳細については、「Implicit styles」をご覧ください。

Style を作成する場合は、TargetType プロパティが常に必要です。 次のコード例は、明示的なスタイルを示しています。

<Style x:Key="labelStyle" TargetType="Label">
    <Setter Property="HorizontalOptions" Value="Center" />
    <Setter Property="VerticalOptions" Value="Center" />
    <Setter Property="FontSize" Value="18" />
</Style>

Style を適用するには、ターゲット オブジェクトが StyleTargetType プロパティ値と一致する VisualElement である必要があります。

<Label Text="Demonstrating an explicit style" Style="{StaticResource labelStyle}" />

ビュー階層で下位にあるスタイルは、上位の定義済みスタイルよりも優先されます。 たとえば、アプリ レベルで Label.TextColorRed に設定する Style を設定すると、Label.TextColorGreen に設定するページ レベルのスタイルによってオーバーライドされます。 同様に、ページ レベルのスタイルはコントロール レベルのスタイルによってオーバーライドされます。 さらに、Label.TextColor がコントロール プロパティに直接設定されている場合は、どのスタイルよりも優先されます。

スタイルはプロパティの変更に応答せず、アプリの実行中は変更されません。 ただし、アプリは動的リソースを使用して実行時にスタイルの変更に動的に応答できます。 詳細については、「Dynamic styles」をご覧ください。

明示的なスタイル

ページ レベルで Style を作成するには、ResourceDictionary をページに追加し、次に 1 つ以上の Style の宣言を ResourceDictionary に含めます。 Style は、宣言に x:Key 属性を指定することで明示的に行われ、ResourceDictionary 内で説明を含むキーを指定します。 明示的なスタイルは、Style プロパティを設定して特定のビジュアル要素に適用する必要があります。

次の例は、ResourceDictionary ページの明示的なスタイルを示し、Label ページのオブジェクトに適用します。

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="labelRedStyle"
               TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="TextColor" Value="Red" />
        </Style>
        <Style x:Key="labelGreenStyle"
               TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="TextColor" Value="Green" />
        </Style>
        <Style x:Key="labelBlueStyle"
               TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="TextColor" Value="Blue" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <Label Text="These labels"
               Style="{StaticResource labelRedStyle}" />
        <Label Text="are demonstrating"
               Style="{StaticResource labelGreenStyle}" />
        <Label Text="explicit styles,"
               Style="{StaticResource labelBlueStyle}" />
        <Label Text="and an explicit style override"
               Style="{StaticResource labelBlueStyle}"
               TextColor="Teal" />
    </StackLayout>
</ContentPage>

この例では、ResourceDictionary はページの Label オブジェクトに明示的に設定される 3 つのスタイルを定義します。 それぞれ Style を使用して、テキストを異なる色で表示しながら、フォント サイズ、水平方向と垂直方向のレイアウト オプションも設定します。 StaticResource マークアップ拡張機能を使用して Style プロパティを設定することで、それぞれの Style は異なる Label に適用されます。 さらに、最終の Label には、Style が設定されていますが、TextColor プロパティも別の Color 値にオーバーライドされます。

暗黙的なスタイル

ページ レベルで Style を作成するには、ResourceDictionary をページに追加し、次に 1 つ以上の Style の宣言を ResourceDictionary に含めます。 Style は、x:Key 属性を指定しないことによって暗黙的に行われます。 スタイルは、TargetType に正確に一致するスコープ内のビジュアル要素に適用されますが、TargetType 値から派生した要素には適用されません。

次のコード例は、ページの ResourceDictionary 内の暗黙的なスタイルを示し、ページの Entry オブジェクトに適用されます。

<ContentPage ...>
    <ContentPage.Resources>
        <Style TargetType="Entry">
            <Setter Property="HorizontalOptions" Value="Fill" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="BackgroundColor" Value="Yellow" />
            <Setter Property="FontAttributes" Value="Italic" />
            <Setter Property="TextColor" Value="Blue" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <Entry Text="These entries" />
        <Entry Text="are demonstrating" />
        <Entry Text="implicit styles," />
        <Entry Text="and an implicit style override"
               BackgroundColor="Lime"
               TextColor="Red" />
        <local:CustomEntry Text="Subclassed Entry is not receiving the style" />
    </StackLayout>
</ContentPage>

この例では、ResourceDictionary は、ページの Entry オブジェクトに暗黙的に設定される、単一の暗黙的スタイルを定義します。 Style は、他の外観オプションを設定しながら、黄色の背景に青色のテキストを表示するために使用されます。 x:Key 属性を指定せずに、Style がページの ResourceDictionary に追加されます。 そのため、Style は、すべての Entry オブジェクトに暗黙的に適用されます。これらのオブジェクトは、StyleTargetType プロパティと正確に一致するからです。 ただし、Style は、サブクラス化された Entry である CustomEntry オブジェクトには適用されません。 さらに、4 番目の Entry は、スタイルの BackgroundColor プロパティと TextColor プロパティを別の Color 値にオーバーライドします。

スタイルを派生型に適用

Style.ApplyToDerivedTypes プロパティを使用すると、TargetType プロパティによって参照される基本タイプから派生したコントロールに、スタイルを適用できます。 そのため、このプロパティを true に設定すると、タイプが TargetType プロパティで指定された基本タイプから派生する場合、単一のスタイルで複数のタイプをターゲティングできるようになります。

次の例は、Button インスタンスの背景色を赤色に設定する、暗黙的なスタイルを示しています。

<Style TargetType="Button"
       ApplyToDerivedTypes="True">
    <Setter Property="BackgroundColor"
            Value="Red" />
</Style>

このスタイルをページ レベルの ResourceDictionary に配置すると、ページ上のすべての Button オブジェクトと、Button から派生するコントロールに適用されます。 ただし、ApplyToDerivedTypes プロパティが設定されていない場合、スタイルは Button オブジェクトにのみ適用されます。

グローバル スタイル

スタイルをアプリのリソース ディクショナリに追加し、グローバルに定義できます。 これらのスタイルは、アプリ全体で使用でき、ページやコントロール間でのスタイルの重複を回避するのに役立ちます。

次の例は、アプリレベルで定義された Style を示しています。


<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Styles"
             x:Class="Styles.App">
    <Application.Resources>        
        <Style x:Key="buttonStyle" TargetType="Button">
            <Setter Property="HorizontalOptions"
                        Value="Center" />
            <Setter Property="VerticalOptions"
                        Value="CenterAndExpand" />
            <Setter Property="BorderColor"
                        Value="Lime" />
            <Setter Property="CornerRadius"
                        Value="5" />
            <Setter Property="BorderWidth"
                        Value="5" />
            <Setter Property="WidthRequest"
                        Value="200" />
            <Setter Property="TextColor"
                        Value="Teal" />
        </Style>
    </Application.Resources>
</Application>

この例では、ResourceDictionary は、単一の明示的なスタイルである buttonStyle を定義します。このスタイルは、Button オブジェクトの外観を設定するために使用されます。

Note

グローバル スタイルは、明示的または暗黙的に定義できます。

次の例は、ページの Button オブジェクトの buttonStyle を使用するページを示しています。

<ContentPage ...>
    <StackLayout>
        <Button Text="These buttons"
                Style="{StaticResource buttonStyle}" />
        <Button Text="are demonstrating"
                Style="{StaticResource buttonStyle}" />
        <Button Text="application styles"
                Style="{StaticResource buttonStyle}" />
    </StackLayout>
</ContentPage>

スタイルの継承

スタイルは、他のスタイルから継承して重複を減らし、再利用できます。 これを行うには、Style.BasedOn プロパティを既存の Style に設定します。 XAML でこれを行うには、BasedOn プロパティを StaticResource マークアップ拡張に設定します。このマークアップ拡張は、以前に作成した Style を参照します。

基本スタイルから継承するスタイルには、新しいプロパティの Setter インスタンスを含めたり、それらのスタイルを使用して基本スタイルのセッターをオーバーライドしたりできます。 さらに、基本スタイルを継承するスタイルは、同じタイプ、または基本スタイルの対象となるタイプから派生したタイプをターゲットにする必要があります。 たとえば、基本スタイルが View オブジェクトをターゲットとする場合、基本スタイルに基づくスタイルは、View オブジェクト、または View クラスから派生するタイプ (Label オブジェクトや Button オブジェクトなど) をターゲットにすることができます。

スタイルは、ビュー階層内の同じレベル以上のスタイルからのみ継承できます。 これは、次のことを意味します。

  • アプリ レベルのスタイルは、他のアプリ レベルのスタイルからのみ継承できます。
  • ページ レベルのスタイルは、アプリ レベルのスタイルやその他のページ レベルのスタイルから継承できます。
  • コントロール レベルのスタイルは、アプリ レベルのスタイル、ページ レベルのスタイル、その他のコントロール レベルのスタイルから継承できます。

次の例は、明示的なスタイルの継承を示しています。

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="baseStyle"
               TargetType="View">
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <StackLayout.Resources>
            <Style x:Key="labelStyle"
                   TargetType="Label"
                   BasedOn="{StaticResource baseStyle}">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="FontAttributes" Value="Italic" />
                <Setter Property="TextColor" Value="Teal" />
            </Style>
            <Style x:Key="buttonStyle"
                   TargetType="Button"
                   BasedOn="{StaticResource baseStyle}">
                <Setter Property="BorderColor" Value="Lime" />
                <Setter Property="CornerRadius" Value="5" />
                <Setter Property="BorderWidth" Value="5" />
                <Setter Property="WidthRequest" Value="200" />
                <Setter Property="TextColor" Value="Teal" />
            </Style>
        </StackLayout.Resources>
        <Label Text="This label uses style inheritance"
               Style="{StaticResource labelStyle}" />
        <Button Text="This button uses style inheritance"
                Style="{StaticResource buttonStyle}" />
    </StackLayout>
</ContentPage>

この例では、baseStyleView オブジェクトをターゲットにし、HorizontalOptions プロパティと VerticalOptions プロパティを設定します。 baseStyle は、どのコントロールにも直接設定されません。 代わりに、labelStylebuttonStyle がそれを継承し、追加のバインド可能なプロパティ値を設定します。 次に、labelStyle オブジェクトと buttonStyle オブジェクトが、LabelButton に設定されます。

重要

暗黙的なスタイルは明示的なスタイルから派生できますが、明示的なスタイルは暗黙的なスタイルから派生できません。

動的なスタイル

スタイルはプロパティの変更に応答せず、アプリの実行中は変更されません。 たとえば、Style をビジュアル要素に割り当てた後、Setter オブジェクトの 1 つが変更、削除、または新しい Setter が追加された場合、それらの変更はビジュアル要素に適用されません。 ただし、アプリは動的リソースを使用して実行時にスタイルの変更に動的に応答できます。

DynamicResource マークアップ拡張は、辞書キーを使用して ResourceDictionary から値をフェッチするという点で、StaticResource マークアップ拡張と似ています。 ただし、StaticResource は単一の辞書検索を実行しますが、DynamicResource は辞書キーへのリンクを維持します。 そのため、キーに関連付けられている辞書エントリが置き換えられると、変更がビジュアル要素に適用されます。 これにより、ランタイム スタイルの変更をアプリで行えます。

次の例は、動的スタイルを示しています。

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="baseStyle"
               TargetType="View">
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>
        <Style x:Key="blueSearchBarStyle"
               TargetType="SearchBar"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="FontAttributes" Value="Italic" />
            <Setter Property="PlaceholderColor" Value="Blue" />
        </Style>
        <Style x:Key="greenSearchBarStyle"
               TargetType="SearchBar">
            <Setter Property="FontAttributes" Value="None" />
            <Setter Property="PlaceholderColor" Value="Green" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <SearchBar Placeholder="SearchBar demonstrating dynamic styles"
                   Style="{DynamicResource blueSearchBarStyle}" />
    </StackLayout>
</ContentPage>

この例では、SearchBar オブジェクトが DynamicResource マークアップ拡張を使用して、blueSearchBarStyle という名前の Style を設定しています。 その後、SearchBarStyle の定義はコードで更新できます。

Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];

この例では、greenSearchBarStyle の定義の値を使用するように blueSearchBarStyle の定義を更新しています。 このコードが実行されると、SearchBargreenSearchBarStyle で定義された Setter オブジェクトを使用するように更新されます。

動的スタイルの継承

Style.BasedOn プロパティを使用して動的スタイルからスタイルを派生させることはできません。 その代わりに、Style クラスには BaseResourceKey プロパティが含まれています。このプロパティを、値を動的に変更できるディクショナリ キーに設定できます。

次の例は、動的スタイルの継承を示しています。

<ContentPage ...>
    <ContentPage.Resources>
        <Style x:Key="baseStyle"
               TargetType="View">
            <Setter Property="VerticalOptions" Value="Center" />
        </Style>
        <Style x:Key="blueSearchBarStyle"
               TargetType="SearchBar"
               BasedOn="{StaticResource baseStyle}">
            <Setter Property="FontAttributes" Value="Italic" />
            <Setter Property="TextColor" Value="Blue" />
        </Style>
        <Style x:Key="greenSearchBarStyle"
               TargetType="SearchBar">
            <Setter Property="FontAttributes" Value="None" />
            <Setter Property="TextColor" Value="Green" />
        </Style>
        <Style x:Key="tealSearchBarStyle"
               TargetType="SearchBar"
               BaseResourceKey="blueSearchBarStyle">
            <Setter Property="BackgroundColor" Value="Teal" />
            <Setter Property="CancelButtonColor" Value="White" />
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <SearchBar Text="SearchBar demonstrating dynamic style inheritance"
                   Style="{StaticResource tealSearchBarStyle}" />
    </StackLayout>
</ContentPage>

この例では、SearchBar オブジェクトは StaticResource マークアップ拡張を使用して、tealSearchBarStyle という名前の Style を参照します。 この Style には、いくつかの追加のプロパティが設定されていて、BaseResourceKey プロパティを使用して blueSearchBarStyle を参照します。 tealSearchBarStyle が変更されないので、派生元となる Style を除き、DynamicResource マークアップ拡張は必要ありません。 したがって、tealSearchBarStyleblueSearchBarStyle へのリンクを維持し、基本スタイルの変更時に更新されます。

blueSearchBarStyle の定義はコードで更新できます。

Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];

この例では、greenSearchBarStyle の定義の値を使用するように blueSearchBarStyle の定義を更新しています。 このコードが実行されると、SearchBargreenSearchBarStyle で定義された Setter オブジェクトを使用するように更新されます。

スタイル クラス

スタイル クラスを使用すると、スタイルの継承に頼ることなく、複数のスタイルをコントロールに適用できます。

スタイル クラスは、StyleClass プロパティを、クラス名を表す string に設定することで作成できます。 x:Key 属性を使用して明示的なスタイルを定義する場合と比べて、この利点は、複数のスタイル クラスを VisualElement に適用できることです。

重要

複数のスタイルが異なる型を対象としている場合、同じクラス名を共有できます。 これにより、同じ名前を持つ複数のスタイル クラスで、異なる型を対象とすることができます。

次の例は、3 つの BoxView スタイル クラスと 1 つの VisualElement スタイル クラスを示しています。

<ContentPage ...>
    <ContentPage.Resources>
        <Style TargetType="BoxView"
               Class="Separator">
            <Setter Property="BackgroundColor"
                    Value="#CCCCCC" />
            <Setter Property="HeightRequest"
                    Value="1" />
        </Style>

        <Style TargetType="BoxView"
               Class="Rounded">
            <Setter Property="BackgroundColor"
                    Value="#1FAECE" />
            <Setter Property="HorizontalOptions"
                    Value="Start" />
            <Setter Property="CornerRadius"
                    Value="10" />
        </Style>    

        <Style TargetType="BoxView"
               Class="Circle">
            <Setter Property="BackgroundColor"
                    Value="#1FAECE" />
            <Setter Property="WidthRequest"
                    Value="100" />
            <Setter Property="HeightRequest"
                    Value="100" />
            <Setter Property="HorizontalOptions"
                    Value="Start" />
            <Setter Property="CornerRadius"
                    Value="50" />
        </Style>

        <Style TargetType="VisualElement"
               Class="Rotated"
               ApplyToDerivedTypes="true">
            <Setter Property="Rotation"
                    Value="45" />
        </Style>        
    </ContentPage.Resources>
</ContentPage>

この例では、SeparatorRoundedCircle の各スタイル クラスの BoxView プロパティが、個別の値に設定されています。 Rotated スタイル クラスの TargetTypeVisualElement で、これは VisualElement インスタンスにのみ適用できることを意味します。 ただし、その ApplyToDerivedTypes プロパティは true に設定されているので、VisualElement から派生する BoxView などのすべてのコントロールに適用できます。 派生型にスタイルを適用する方法について詳しくは、「スタイルを派生型に適用」をご覧ください。

スタイル クラスを使用するには、コントロールの StyleClass プロパティ (IList<string> 型) に、スタイル クラス名のリストを設定します。 コントロールの型がスタイル クラスの TargetType と一致すれば、そのスタイル クラスが適用されます。

次の例では、3 つの BoxView インスタンスに、それぞれ異なるスタイル クラスを設定しています。

<ContentPage ...>
    <ContentPage.Resources>
        ...
    </ContentPage.Resources>
    <StackLayout>
        <BoxView StyleClass="Separator" />       
        <BoxView WidthRequest="100"
                 HeightRequest="100"
                 HorizontalOptions="Center"
                 StyleClass="Rounded, Rotated" />
        <BoxView HorizontalOptions="Center"
                 StyleClass="Circle" />
    </StackLayout>
</ContentPage>    

この例では、1 つ目の BoxView は行区切りのスタイルになり、3 つ目の BoxView は円形になります。 2 つ目の BoxView には 2 つのスタイル クラスが適用されていて、角が丸くなり、45 度回転します。

Screenshot of BoxViews styled with style classes.

重要

StyleClass プロパティは IList<string> 型であるため、コントロールに複数のスタイル クラスを適用できます。 この場合、スタイル クラスはリストの昇順に適用されます。 したがって、複数のスタイル クラスに同一のプロパティが設定されている場合、リストの最上位にあるスタイル クラスのプロパティが優先されます。