使用 XAML 设置应用样式

.NET Multi-platform App UI (.NET MAUI) 应用通常包含多个具有相同外观的控件。 例如,应用可能有多个包含相同字体选项和布局选项的 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 类将属性值的集合分组到一个对象中,然后将其应用于多个视觉元素,从而设置应用的样式。 这有助于减少重复性标记,并允许更轻松地更改应用外观。

尽管样式主要针对基于 XAML 的应用,但它们也可以在 C# 中创建:

  • 在 XAML 中创建的 Style 对象通常在分配给控件、页面的 Resources 集合或在分配给应用的 Resources 集合的 ResourceDictionary 中定义。
  • 在 C# 中创建的 Style 对象通常在页面的类中定义,或者在可全局访问的类中定义。

选择在何处定义 Style 会影响其应用范围:

  • 在控件级别定义的 Style 实例只能应用于控件及其子级。
  • 在页面级别定义的 Style 实例只能应用于页面及其子级。
  • 在应用级别定义的 Style 实例可以应用于整个应用。

每个 Style 对象都包含一个或多个 Setter 对象的集合,其中每个 Setter 都有一个 Property 和一个 ValueProperty 是应用样式的元素的可绑定属性的名称,而 Value 是应用于属性的值。

每个 Style 对象可以是显式对象,也可以是隐式对象:

  • 显式Style 对象可通过指定 TargetTypex: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,目标对象必须是与 StyleTargetType 属性值匹配的 VisualElement

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

视图层次结构中等级较低的样式优先于较高等级定义的样式。 例如,在应用级别设置 Style 以将 Label.TextColor 设置为 Red 会被将 Label.TextColor 设置为 Green 的页面级样式重写。 同样,页面级样式将由控件级样式重写。 此外,如果直接在控件属性上设置 Label.TextColor,则优先于任何样式。

样式不会响应属性更改,并且在应用持续期间保持不变。 但是,应用可以使用动态资源在运行时动态响应样式更改。 有关详细信息,请参阅动态样式

显式样式

要在页面级别创建 Style,必须将 ResourceDictionary 添加到页面中,然后才能在 ResourceDictionary 中包含一个或多个 Style 声明。 可以通过为其声明提供 x:Key 属性来显式表示 Style,该属性在 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 对象上显式设置的三种样式。 每个 Style 用于以不同的颜色显示文本,同时还设置字体大小以及水平和垂直布局选项。 可以使用 StaticResource 标记扩展设置其 Style 属性来将每个 Style 都应用于不同的 Label。 此外,虽然最终 Label 上设置了 Style,但它也会将 TextColor 属性重写为其他 Color 值。

隐式样式

要在页面级别创建 Style,必须将 ResourceDictionary 添加到页面中,然后才能在 ResourceDictionary 中包含一个或多个 Style 声明。 通过不指定 x:Key 属性来隐式表示 Style。 然后,该样式将应用于与 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 不应用于子类化 EntryCustomEntry 对象。 此外,第四个 Entry 将样式的 BackgroundColorTextColor 属性重写为不同的 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 对象的外观。

注意

全局样式可以是显式样式,也可以是隐式样式。

以下示例显示页面在其 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 属性设置为引用此前创建的 StyleStaticResource 标记扩展即可达成此目的。

从基样式继承的样式可以包括新属性的 Setter 实例,也可以使用它们来重写基样式的资源库。 此外,从基样式继承的样式必须面向同一类型,或者从基样式所面向的类型派生的类型。 例如,如果基样式面向 View 对象,则基于基样式的样式可以面向 View 对象或派生自 View 类的类型,如 LabelButton 对象。

样式只能从视图层次结构中相同级别或更高级别的样式继承。 这表示:

  • 应用级样式只能继承自其他应用级样式。
  • 页面级样式可以从应用级样式和其他页面级样式继承。
  • 控件级样式可以从应用级样式、页面级样式和其他控件级样式继承。

以下示例显示了显式样式继承:

<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 对象,并设置 HorizontalOptionsVerticalOptions 属性。 不直接在任何控件上设置 baseStyle。 相反,labelStylebuttonStyle 从它继承,并设置其他可绑定属性值。 然后,在 LabelButton 上设置 labelStylebuttonStyle 对象。

重要

隐式样式可以派生自显式样式,但显式样式不能派生自隐式样式。

动态样式

样式不会响应属性更改,并且在应用持续期间保持不变。 例如,向视觉对象元素分配 Style 后,如果其中一个 Setter 对象被修改、删除或添加了新 Setter,则不会将更改应用于视觉对象元素。 但是,应用可以使用动态资源在运行时动态响应样式更改。

DynamicResource 标记扩展类似于 StaticResource 标记扩展,两者都使用字典键从 ResourceDictionary 中提取值。 但是,当 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 标记扩展来设置命名为 blueSearchBarStyleStyle。 然后,SearchBar 可以在代码中更新其 Style 定义:

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

在此示例中,将更新 blueSearchBarStyle 定义以使用 greenSearchBarStyle 定义中的值。 执行此代码时,SearchBar 将更新以使用在 greenSearchBarStyle 中定义的 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 标记扩展来引用命名为 tealSearchBarStyleStyle。 此 Style 设置一些其他属性,并使用 BaseResourceKey 属性引用 blueSearchBarStyle。 不需要 DynamicResource 标记扩展,因为 tealSearchBarStyle 不会更改,其派生源 Style 除外。 因此,tealSearchBarStyle 在基本样式发生更改时,维护并更新指向的 blueSearchBarStyle 链接。

可以在代码中更新 blueSearchBarStyle 定义:

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

在此示例中,将更新 blueSearchBarStyle 定义以使用 greenSearchBarStyle 定义中的值。 执行此代码时,SearchBar 将更新以使用在 greenSearchBarStyle 中定义的 Setter 对象。

样式类

样式类允许将多个样式应用于控件,而无需采用样式继承。

可以通过将 Style 上的 Class 属性设置为表示类名的 string 来创建。 与使用 x:Key 属性定义显式样式一样,这一优势在于可将多个样式类应用于一个 VisualElement

重要

如果多个样式面向不同类型,则可以共享相同的类名。 这使多个样式类(同名)可以面向不同的类型。

以下示例显示了三个 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>

在此示例中,SeparatorRoundedCircle 样式类分别设置 BoxView 属性为特定值。 Rotated 样式类有一个 VisualElementTargetType,这意味着它只能应用于 VisualElement 实例。 但是,其 ApplyToDerivedTypes 属性设置为 true,这可确保它可以应用于派生自 VisualElement 的任何控件,例如 BoxView。 有关将样式应用于派生类型的详细信息,请参阅 将样式应用于派生类型

可以通过将控件的 StyleClass 属性(类型为 IList<string>)设置为样式类名称列表来使用样式类。 将应用样式类,前提是控件的类型与样式类的 TargetType 匹配。

以下示例显示了三个 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 样式为行分隔符,而第三个 BoxView 个为圆形。 第二个 BoxView 应用了两个样式类,为它提供圆角并旋转 45 度:

Screenshot of BoxViews styled with style classes.

重要

可以将多个样式类应用于控件,因为 StyleClass 属性属于 IList<string> 类型。 发生这种情况时,样式类按升序列表顺序应用。 因此,当多个样式类设置相同的属性时,位于最高列表位置的样式类中的属性将优先。