使用 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 对象可以是显式对象,也可以是隐式对象:

  • 通过指定 TargetTypex:Key 值,并将目标元素的 Style 属性设置为 x:Key 引用来定义显式 Style 对象。 有关详细信息,请参阅显式样式
  • 通过仅指定 TargetType来定义隐式 Style 对象。 然后,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}" />

视图层次结构中等级较低的样式优先于定义为较高等级的样式。 例如,设置在应用级别将 Label.TextColor 设置为 RedStyle 会被将 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 属性引用的基类型的控件。 因此,如果类型派生自 TargetType 属性中指定的基类型,则将此属性设置为 true 可使单个样式面向多个类型。

以下示例显示将 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。 然后,可以在代码中对 SearchBarStyle 定义进行更新:

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 样式类的 TargetTypeVisualElement,这意味着它只能应用于 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 度:

使用样式类设置样式的 BoxViews 的屏幕截图。

重要

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