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 にはそれぞれ Property
と Value
があります。 Property
は、スタイルが適用される要素のバインド可能なプロパティの名前で、Value
はプロパティに適用される値です。
各 Style オブジェクトは、明示的または暗黙的なものとなります。
- "明示的な" Style オブジェクトは、
TargetType
とx:Key
の値を指定し、ターゲット要素の Style プロパティをx:Key
参照に設定することで定義します。 詳細については、「明示的なスタイル」をご覧ください。 - "暗黙的な" Style オブジェクトは、
TargetType
のみを指定することで定義します。 その後、Style オブジェクトはその型のすべての要素に自動的に適用されます。 ただし、TargetType
のサブクラスには、Style は自動的には適用されません。 詳細については、「暗黙的なスタイル」をご覧ください。
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 を適用するには、ターゲット オブジェクトが、Style の TargetType
プロパティの値と一致する VisualElement である必要があります。
<Label Text="Demonstrating an explicit style" Style="{StaticResource labelStyle}" />
ビュー階層で下位にあるスタイルは、上位の定義済みスタイルよりも優先されます。 たとえば、アプリ レベルで Label.TextColor
を Red
に設定する Style を設定すると、この設定は、Label.TextColor
を Green
に設定するページ レベルのスタイルによりオーバーライドされます。 同様に、ページ レベルのスタイルはコントロール レベルのスタイルによってオーバーライドされます。 さらに、Label.TextColor
がコントロール プロパティで直接設定されている場合は、どのスタイルよりも優先されます。
スタイルはプロパティの変更には反応せず、アプリの実行中は変動しません。 一方、アプリは動的リソースを使用し、実行時にスタイルの変更に動的に反応する場合があります。 詳細については、「動的スタイル」をご覧ください。
明示的なスタイル
ページ レベルで 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 は、テキストをそれぞれ異なる色で表示するために使用されます。また、フォント サイズ、水平方向と垂直方向のレイアウト オプションも設定します。 各 Style は、StaticResource
マークアップ拡張機能を使用して 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>
この例では、ページの Entryオブジェクトに暗黙的に設定される 1 つの暗黙的なスタイルが ResourceDictionary に定義されています。 Style を使用して、黄色の背景に青いテキストを表示するように設定しているほか、他の外観オプションも設定されています。 x:Key
属性を指定せずに、ページの ResourceDictionary に Style が追加されています。 したがって、Style はすべての Entry オブジェクトに暗黙的に適用されます。これらのオブジェクトが Style の TargetType
プロパティに厳密に一致するからです。 ただし、Style は、Entry のサブクラスである CustomEntry
オブジェクトには適用されません。 さらに、4 番目の Entry は、スタイルの BackgroundColor
プロパティと TextColor
プロパティを異なる Color 値にオーバーライドします。
派生型にスタイルを適用する
Style.ApplyToDerivedTypes
プロパティを使用すると、TargetType
プロパティで参照される基本データ型から派生したコントロールにスタイルを適用できます。 したがって、TargetType
プロパティで指定された基本データ型から派生している型であれば、このプロパティを true
に設定することで、1 つのスタイルで複数の型をターゲットにできます。
次の例は、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
という 1 つの 明示的なスタイルが定義されており、これが 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 でこれを実現するには、前に作成した Style を参照する StaticResource
マークアップ拡張に BasedOn
プロパティを設定します。
基本スタイルから継承するスタイルには、新しいプロパティの 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>
この例では、baseStyle
は View オブジェクトを対象とし、HorizontalOptions
プロパティと VerticalOptions
プロパティを設定します。 baseStyle
は、どのコントロールにも直接設定されません。 代わりに、labelStyle
と buttonStyle
がそれを継承し、追加のバインド可能なプロパティ値を設定します。 次に、labelStyle
オブジェクトと buttonStyle
オブジェクトが Label と Button に設定されます。
重要
暗黙的なスタイルを明示的なスタイルから派生させることはできますが、明示的なスタイルを暗黙的なスタイルから派生させることはできません。
動的なスタイル
スタイルはプロパティの変更には反応せず、アプリの実行中は変動しません。 たとえば、Style をビジュアル要素に割り当てた後、Setter オブジェクトの 1 つが変更または削除されたり、新しい Setter が追加されたりした場合、その変更はビジュアル要素に適用されません。 一方、アプリは動的リソースを使用し、実行時にスタイルの変更に動的に反応する場合があります。
DynamicResource
マークアップ拡張と StaticResource
マークアップ拡張は、両方ともディクショナリ キーを使用して ResourceDictionary から値を取得するという点で、似ています。 ただし、StaticResource
が 1 回だけディクショナリ検索を実行するのに対して、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 を設定しています。 その後、SearchBar の Style 定義をコードで更新できます。
Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];
この例では、greenSearchBarStyle
定義の値を使用するように blueSearchBarStyle
定義が更新されます。 このコードが実行されると、greenSearchBarStyle
で定義されている Setter オブジェクトを使用するように SearchBar が更新されます。
動的スタイルの継承
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
を参照します。 DynamicResource
マークアップ拡張は必要ありません。派生元の Style を除き、tealSearchBarStyle
が変更されないからです。 したがって、tealSearchBarStyle
は blueSearchBarStyle
へのリンクを保持し、基本スタイルの変更時に更新されます。
blueSearchBarStyle
定義はコードで更新できます。
Resources["blueSearchBarStyle"] = Resources["greenSearchBarStyle"];
この例では、greenSearchBarStyle
定義の値を使用するように blueSearchBarStyle
定義が更新されます。 このコードが実行されると、greenSearchBarStyle
で定義されている Setter オブジェクトを使用するように SearchBar が更新されます。
スタイル クラス
スタイル クラスを使用すると、スタイルの継承を使用せずに、複数のスタイルをコントロールに適用できます。
スタイル クラスは、Style の Class
プロパティを、クラス名を表す string
に設定することで作成できます。 この方法が、x:Key
属性を使用して明示的なスタイルを定義するよりも好都合な点は、複数のスタイル クラスを VisualElement に適用できることです。
重要
複数のスタイルが異なる型を対象としている場合は、それらのスタイルで同じクラス名を共有できます。 これにより、同じ名前を持つ複数のスタイル クラスで、異なる型を対象とすることができます。
次の例は、3 つの BoxView スタイル クラスと 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>
この例では、Separator
、Rounded
、Circle
の各スタイル クラスが、BoxView プロパティを特定の値に設定します。 Rotated
スタイル クラスには VisualElement という TargetType
があります。つまり、VisualElement インスタンスにのみ適用できます。 ただし、その ApplyToDerivedTypes
プロパティは true
に設定され、BoxView などの、VisualElement から派生するあらゆるコントロールに確実に適用できるようになります。 派生型にスタイルを適用する方法の詳細については、「派生型にスタイルを適用する」を参照してください。
スタイル クラスを使用するには、コントロールの 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>
この例では、最初の BoxView が行区切り記号にスタイル設定されるのに対して、3 番目の BoxView は円形です。 2 番目の BoxView には 2 つのスタイル クラスが適用されており、角が丸く 45 度回転した形になっています。
重要
StyleClass
プロパティが IList<string>
型なので、コントロールに複数のスタイル クラスを適用できます。 この場合、スタイル クラスは昇順に適用されます。 したがって、複数のスタイル クラスで同じプロパティが設定されている場合、リストの最も高い位置にあるスタイル クラスのプロパティが優先されます。
.NET MAUI