介绍
在整个 UI 中使用相同的字体和颜色可创建一致的外观。.NET MAUI 提供了一种在一个位置定义这些值的方法,并在使用这些值的任何位置查找它们。重用值可确保整个应用的一致性,并使更新变得简单。
假设您正在构建一个名为 TipCalculator 的移动应用程序。该应用程序用于酒店业,并允许服务员快速计算任何服务的小费。您的公司最近改变了其企业品牌的外观。你的工作是更新应用的 UI 以匹配新外观。您需要更改字体、文本颜色和背景颜色。您希望使这种更新变得容易,因为随着公司的发展,将会有更多的品牌变化。
在本模块中,你将学习如何在代码和 XAML 中定义和应用资源。您还将了解如何将多个设置分组到一个样式中,以便一次应用所有设置。
备注
此模块需要 Visual Studio 2022。您可以使用 Windows 或 Mac。如果你在 Windows 上运行,请确保已安装“使用 .NET 进行移动开发”工作负荷。如果需要将其添加到安装中,请参阅文档。如果你在 Mac 上运行,则标准的 Visual Studio for Mac 安装包括使用 .NET MAUI 构建应用所需的一切。
学习目标
在本模块中,您将学习如何:
- 在 MAUI XAML 用户界面中创建和使用静态资源
- 创建和使用动态资源
- 使用样式创建一致的用户界面
- 创建和使用应用程序范围的资源
- 使用内置样式应用用户辅助功能选择
先决条件
- 安装了 MAUI 工作负载的 Visual Studio 2022
- 熟悉 C# 和 .NET
定义和使用资源
资源类似于编程语言中的符号常量。您可以在一个地方定义它,并在需要它的任何地方引用它。您的代码将更易于阅读,因为您使用描述性名称而不是“魔术”值。如果需要更改值,则只需更新定义。
在本模块中,你将了解如何使用资源从 XAML 中删除硬编码值。
什么是资源?
资源是可以在 UI 中共享的任何对象。最常见的示例是字体、颜色和大小。但是,您也可以将复杂对象(如 Style 和 OnPlatform 实例)存储为资源。
在 XAML 或代码中定义资源。然后,在 XAML 或代码中应用它。通常,你将完全在 XAML 中工作,尽管稍后你将看到代码有用的一些情况。
考虑一个例子。假设您希望在页面上的控件上使用相同的 TextColor 值。如果使用硬编码的值,则 XAML 将如下所示。请注意文本颜色的值在两个控件中的重复方式。
<Label TextColor="Blue" FontSize="14">
<Button TextColor="Blue" FontSize="14">
您可以将其定义为资源,而不是重复文本颜色。该定义类似于以下 XAML:
<Color x:Key="PageControlTextColor">Blue</Color>
请注意,定义的元素如何具有为资源命名的 x:Key 属性。使用此键在 XAML 中查找资源。
必须先将资源存储在资源字典中,然后才能使用资源。
什么是资源字典?
ResourceDictionary 是一个 .NET MAUI 库类,可针对 UI 资源进行自定义。它是一个字典,因此它存储键/值对。键的类型限制为 String,而值可以是任何对象。
每个 .NET MAUI XAML 页都有一个名为 Resources 的属性,该属性可以保存 ResourceDictionary 对象。默认情况下,该属性为 null,因此您需要先创建字典实例,然后才能使用它。下面的代码演示如何创建 ResourceDictionary 对象并将其分配给 ContentPage 的 Resources 属性:
<ContentPage.Resources>
<ResourceDictionary>
...
</ResourceDictionary>
</ContentPage.Resources>
.NET MAUI XAML 具有内置的便利语法,只要您开始使用 Resources 属性,它就会自动创建字典实例。前面的示例可以简化为以下代码:
<ContentPage.Resources>
...
</ContentPage.Resources>
应用中的每个页面都可以有自己的字典。您可以使用这些特定于页面的词典来存储将在该页面上专门使用的资源。
备注
页面上的每个控件也可以有自己的资源字典。例如,可以将资源目录添加到 Label 控件,如下所示:
<Label Text="Hello, World!"
...
<Label.Resources>
...
</Label.Resources>
</Label>
除了可以保存子元素的布局和视图之外,在控件级别执行此操作并不常见。
创建资源
若要创建资源,请在页面的“资源”属性中声明该资源。下面的示例创建前面描述的文本颜色资源
<ContentPage.Resources>
<Color x:Key="PageControlTextColor">Blue</Color>
</ContentPage.Resources>
为资源选择键时,请选择反映资源用途而不是资源值的名称。例如,要将标签的背景设置为红色,d不要使用 RedColor 作为键,而应改用 BackgroundColor。
使用静态资源应用资源
StaticResource 是一个标记扩展,用于在资源字典中查找资源。提供资源的键,标记扩展返回相应的值。下面的 XAML 标记显示了一个示例,该示例创建并使用名为 PageControlTextColor 的资源。示例中标签控件的 XAML 标记使用 StaticResource 标记扩展来检索值。Color
<ContentPage.Resources>
<Color x:Key="PageControlTextColor">Blue</Color>
</ContentPage.Resources>
...
<Label TextColor="{StaticResource PageControlTextColor}" ... />
该扩展称为 StaticResource,因为该扩展仅计算一次。创建目标对象时,将进行字典查找。如果字典中的资源值发生更改,则不会更新 target 属性。
警告
如果未找到密钥,StaticResource 将引发运行时异常。
XAML 内部类型
本单元开头提供的原始示例设置了 TextColor 属性和 FontSize 属性:
<Label TextColor="Blue" FontSize="14">
<Button TextColor="Blue" FontSize="14">
FontSize 的类型为 Double。若要为此值创建资源,请使用 XAML 规范中定义的 XAML 内部类型之一。XAML 规范为许多 C# 简单类型定义了类型名称。下面的代码演示每个内部类型的示例资源。
<ContentPage.Resources>
<x:String x:Key="...">Hello</x:String>
<x:Char x:Key="...">X</x:Char>
<x:Single x:Key="...">31.4</x:Single>
<x:Double x:Key="...">27.1</x:Double>
<x:Byte x:Key="...">8</x:Byte>
<x:Int16 x:Key="...">16</x:Int16>
<x:Int32 x:Key="...">32</x:Int32>
<x:Int64 x:Key="...">64</x:Int64>
<x:Decimal x:Key="...">12345</x:Decimal>
<x:TimeSpan x:Key="...">1.23:5959</x:TimeSpan>
<x:Boolean x:Key="...">True</x:Boolean>
</ContentPage.Resources>
为资源设置特定于平台的值
通常需要在平台之间稍微调整应用的 UI。定义特定于平台的值的标准方法是在定义资源时使用 OnPlatform 对象。例如,下面的代码演示如何创建在 iOS、Android、macOS (Mac Catalyst) 和 Windows (WinUI) 上引用不同文本颜色的资源。
<ContentPage.Resources>
<OnPlatform x:Key="textColor" x:TypeArguments="Color">
<On Platform="iOS" Value="Silver" />
<On Platform="Android" Value="Green" />
<On Platform="WinUI" Value="Yellow" />
<On Platform="MacCatalyst" Value="Pink" />
</OnPlatform>
</ContentPage.Resources>
...
<Label TextColor="{StaticResource textColor}" ... />
练习:使用页面级资源
本模块中的所有练习都与预构建的 TipCalculator 应用程序相关。您将在整个模块中修改和改进此应用程序。在本练习中,你将使用页级资源来消除重复的 XAML 值。
打开入门级解决方案
备注
如果您计划从 Windows 在 Android 上运行和调试 .NET MAUI 应用程序,最好将练习内容克隆或下载到较短的文件夹路径(如 C:\dev)中,以避免生成生成的文件超过最大路径长度。
从 GitHub 克隆或下载锻炼存储库。
使用 Visual Studio 从 exercise1/TipCalculator 文件夹中打开初学者解决方案。
验证它是否在您的环境中生成和运行。(任何平台都可以。
花几分钟时间检查并运行应用程序,以便了解它的行为方式。该应用程序提供两个页面。标准提示页面是一个简单的提示计算器。输入一个值,页面将计算小费 (15%) 和应付总额。下图显示了在安卓设备上运行的应用程序:
使用“浅色”和“深色”按钮可以更改页面的颜色主题。默认值为“浅色”主题。如果选择“深色”,背景和文本的颜色将反转。“使用自定义计算器”按钮将显示切换到“自定义提示页”页。使用此页,您可以使用滑块更改提示百分比。您还可以选择 15% 和 20% 按钮,根据预定义的费率计算小费。
查找重复的 XAML
在 Visual Studio 中,打开 StandardTipPage.xaml 文件。
查找设置布局根网格的背景色的 XAML 标记。请注意,它使用硬编码的值。
<Grid x:Name ="LayoutRoot" BackgroundColor="Silver" Padding="10">
查找 XAML 标记,该标记将“左列”中标签的文本颜色设置为 Navy,并将字体大小设置为 22。请注意,这些相同的值用于三个标签上。
<!-- Left column = static labels -->
<Label x:Name="billLabel" Text="Bill" TextColor="Navy" FontSize="22" ... />
<Label x:Name="tipLabel" Text="Tip" TextColor="Navy" FontSize="22" ... />
<Label x:Name="totalLabel" Text="Total" TextColor="Navy" FontSize="22" ... />
查找将“右列”中的标签颜色设置为 Navy 并将磅大小设置为 22 的 XAML 代码。请注意,这些相同的值用于两个标签。某些属性设置似乎形成了一个逻辑组。例如,海军和 22 的组合用于多个标签。
<!-- Right column = user input and calculated-value output -->
<Entry ... />
<Label x:Name="tipOutput" Text="0.00" TextColor="Navy" FontSize="22" ... />
<Label x:Name="totalOutput" Text="0.00" TextColor="Navy" FontSize="22" ... />
考虑修改 TextColor 和 FontSize 值所涉及的工作。您需要在五个地方更改它。
定义资源
现在,您已经看到了应用程序中存在的所有重复代码。让我们在 XAML 中创建资源,以开始消除一些重复的代码。
打开 StandardTipPage.xaml 文件。
在 ContentPage.Resources 部分中定义颜色资源。为资源指定 bgColor 的 x:Key 资源 ID 和值 #C0C0C0(也可以使用颜色的名称)。Silver
定义第二个颜色资源。为它指定 fgColor 的 x:Key 资源 ID 和值 #0000AD(也可以使用颜色的名称)。Navy
定义 ID 为 fontSize 的 x:Double 资源。将此资源的值设置为 22。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
...>
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="bgColor">#C0C0C0</Color>
<Color x:Key="fgColor">#0000AD</Color>
<x:Double x:Key="fontSize">22</x:Double>
</ResourceDictionary>
</ContentPage.Resources>
<Grid x:Name ="LayoutRoot" ...>
...
使用静态资源
现在,让我们应用您创建的资源。
使用 StaticResource 标记扩展将 bgColor 资源应用于 LayoutRoot Grid 控件的“背景”属性。
...
<Grid x:Name ="LayoutRoot" BackgroundColor="{StaticResource bgColor}" Padding="10">
将 fgColor 资源应用于当前将 TextColor 设置为 Navy 的所有 Label 控件的 TextColor 属性。此外,将硬编码的字体大小替换为 fontSize 静态资源。
...
<!-- Left column = static labels -->
<Label x:Name="billLabel" Text="Bill" TextColor="{StaticResource fgColor}" FontSize="{StaticResource fontSize}" ... />
<Label x:Name="tipLabel" Text="Tip" TextColor="{StaticResource fgColor}" FontSize="{StaticResource fontSize}" ... />
<Label x:Name="totalLabel" Text="Total" TextColor="{StaticResource fgColor}" FontSize="{StaticResource fontSize}" ... />
...
运行应用程序。验证 StandardTipPage 在启动时是否仍会在浅色背景上显示深色文本,就像以前一样。
备注
此时不要担心自定义提示页面或浅色和深色主题的样式;稍后您将解决这些问题。
使用和更新动态资源
在前面的单元中,你在 XAML 中定义了一个资源,并将其用作静态值。但是,在某些情况下,StaticResource 是不合适的。请考虑以下方案:
- 假设你想要实现允许用户在运行时更改应用外观的颜色主题。StaticResource 标记扩展只执行一次字典查找,因此它无法动态更新 UI。
- 将用户首选项存储在 Web 服务器上,并在应用程序启动时加载它们。如果在字典中找不到键,StaticResource 标记扩展将引发异常。
本单元说明如何使用动态资源处理此类问题。
如何在运行时更新资源
将资源存储在资源字典中。您可以编写代码以在运行时更新这些资源。您甚至可以添加新资源或删除现有资源。
请考虑以下示例:
<ContentPage.Resources>
<Color x:Key="PanelBackgroundColor">Blue</Color>
</ContentPage.Resources>
假设您要在应用程序运行时更改 PanelBackgroundColor 资源的值。可以将如下所示的代码添加到页面的代码隐藏文件中,以访问 Resources 属性。下面的示例将上一个 XAML 示例中的资源值更新为其他颜色。
this.Resources["PanelBackgroundColor"] = Colors.Green;
什么是动态资源?
DynamicResource 是另一个标记扩展,用于在资源字典中查找资源。它类似于 StaticResource,因为它在创建目标对象时执行字典查找。但它也会侦听字典中对资源的更改。如果字典中的资源值发生更改,动态资源将自动更新 UI。
DynamicResource比StaticResource具有优势。如果 DynamicResource 在字典中找不到键,则会使该属性处于未设置状态。与 StaticResource 不同,缺少密钥不是错误,也不会引发异常。
备注
DynamicResource 标记扩展的性质会对应用程序造成很小的性能损失。尽管可以在 XAML 页中使用 DynamicResource 代替 StaticResource,但如果资源未更改,则应使用 StaticResource 标记扩展来引用它。
若要使用前面示例中更新的背景色,可以在 XAML 代码中应用动态资源,如下所示:
<ContentPage ...>
<ContentPage.Resources>
<Color x:Key="PanelBackgroundColor">Blue</Color>
</ContentPage.Resources>
<StackLayout BackgroundColor="{DynamicResource PanelBackgroundColor}">
...
</StackLayout>
</ContentPage>
如果 PanelBackgroundColor 资源的值发生更改,则 StackLayout 控件的“背景颜色”值将自动更新。
练习:使用动态资源更新元素
在本练习中,你将使用动态资源标记扩展在资源值更改时更新 TipCalculator UI。
本练习是上一个练习的延续。使用现有解决方案作为这些步骤的起点,或在上一练习中克隆的存储库的 exercise2/TipCalculator 文件夹中打开 TipCalculator 项目。
查找重复代码
该应用程序为标准提示页面实现了简单的“浅”和“暗”配色方案。在这里,您将检查用于更改颜色的代码。
打开 StandardTipPage.xaml.cs 文件。
找到更新 UI 颜色的两个事件处理程序。
private Color colorNavy = Colors.Navy;
private Color colorSilver = Color.Silver;
...
void OnLight(object sender, EventArgs e)
{
LayoutRoot.BackgroundColor = colorSilver;
tipLabel.TextColor = colorNavy;
billLabel.TextColor = colorNavy;
totalLabel.TextColor = colorNavy;
tipOutput.TextColor = colorNavy;
totalOutput.TextColor = colorNavy;
}
void OnDark(object sender, EventArgs e)
{
LayoutRoot.BackgroundColor = colorNavy;
tipLabel.TextColor = colorSilver;
billLabel.TextColor = colorSilver;
totalLabel.TextColor = colorSilver;
tipOutput.TextColor = colorSilver;
totalOutput.TextColor = colorSilver;
}
...
请注意代码如何单独更新每个控件的颜色,从而导致重复的代码。
从代码更新资源
您将首先编写代码来更新存储在页面资源字典中的一些资源。
从 OnLight 方法中删除所有代码。
将下面显示的代码添加到 OnLight 方法中。此代码将页面资源字典中的 fgColor 资源设置为 colorNavy 变量中的值,并将 bgColor 资源设置为 colorSilver 变量中的值。colorNavy 和 colorSilver 变量使用静态 Color.FromRgb 方法,这样可以轻松地将十六进制值转换为颜色。
void OnLight(object sender, EventArgs e)
{
Resources["fgColor"] = colorNavy;
Resources["bgColor"] = colorSilver;
}
对 OnDark 方法重复前两个步骤,但反转颜色;将 fgColor 设置为 colorSilver,将 bgColor 设置为 colorNavy。
void OnDark(object sender, EventArgs e)
{
Resources["fgColor"] = colorSilver;
Resources["bgColor"] = colorNavy;
}
运行应用。选择“深色”和“浅色”按钮。UI 不会更改。即使代码更改了字典中的资源值,新值也不会传播到 UI。问题是你正在使用静态资源标记扩展来设置 XAML 代码中的值。
动态更新 UI
若要解决此问题,你将修改 XAML,以便将更新的资源值加载到 UI 中。
停止应用,然后打开 StandardTipPage.xaml 文件。
找到从资源值分配颜色的所有位置。将静态资源标记扩展的使用替换为动态资源,如下面的示例所示。
<Grid x:Name ="LayoutRoot" BackgroundColor="{DynamicResource bgColor}" Padding="10">
...
<Label x:Name="billLabel" Text="Bill" TextColor="{DynamicResource fgColor}" ... />
<Label x:Name="tipLabel" Text="Tip" TextColor="{DynamicResource fgColor}" ... />
<Label x:Name="totalLabel" Text="Total" TextColor="{DynamicResource fgColor}" ... />
...
备注
不要将 FontSize 属性从 StaticResource 更改为 DynamicResource。
运行应用。选择“深色”和“浅色”按钮。UI 现在可以正确更新。
使用样式创建一致的 UI
资源非常适合避免在 XAML 标记中使用硬编码的重复值,但应用这些资源可能很繁琐。单独分配每个属性值,这可能会导致混乱和冗长的 XAML。本单元介绍如何将多个设置分组到一个样式中,这有助于整理代码并使其更易于维护。
资源如何使 XAML 混乱
资源为单个属性提供值。但是,使用大量资源可能会导致冗长的 XAML。假设您希望为按钮提供自定义外观。首先为所需的值创建资源。然后,将每个资源应用于所有按钮。下面的代码演示 XAML 标记如何查找两个按钮。
<Button
Text = "OK"
BackgroundColor = "{StaticResource highlightColor}"
BorderColor = "{StaticResource borderColor}"
BorderWidth = "{StaticResource borderWidth}"
TextColor = "{StaticResource textColor}" />
<Button
Text = "Cancel"
BackgroundColor = "{StaticResource highlightColor}"
BorderColor = "{StaticResource borderColor}"
BorderWidth = "{StaticResource borderWidth}"
TextColor = "{StaticResource textColor}" />
请注意如何在每个按钮上设置相同的五个属性。使用资源消除了对重复的硬编码值的需求。但是,这种类型的 XAML 标记很快就会变得难以阅读。此外,如果要为每个控件设置大量属性,则很容易意外省略其中一个属性,从而导致控件外观不一致。解决方案是创建一个同时分配所有四个属性的样式。
什么是setter?
Setter 是用于创建样式的关键组件。
setter 是属性/值对的容器。您可以将 setter 视为表示赋值语句。指定要分配的属性和要应用的值。通常在 XAML 标记中创建 Setter 对象。下面的示例为 TextColor 属性创建一个 Setter 对象。
<Setter Property="TextColor" Value="White" />
可以将资源用于 setter 中的值,如下面的代码所示。当您想要在多个 setter 中使用相同的值时,此技术非常有用。
<Setter Property="TextColor" Value="{StaticResource textColor}" />
备注
在 setter 中指定的属性值必须作为可绑定属性实现。.NET MAUI 中以后缀 Property 结尾的控件上的所有属性都是可绑定的属性。如果尝试在 setter 中使用 TextColor 等属性,请确保该控件具有名为 TextColorProperty 的相应可绑定属性。在实践中,几乎所有要在 setter 中使用的属性都是以这种方式实现的。
什么是风格?
样式是针对特定类型控件的 setter 的集合。.NET MAUI 需要目标类型,以便它可以确保 setter 中的属性存在于该类型上。
下面的代码演示一个样式,该样式结合了上一示例中的四个值。请注意,TargetType 设置为 Button,并且 setter 中的所有属性都是 Button 类的成员。不能将此样式用于标注,因为标注类不包含 BorderColor 或 BorderWidth 属性。
<Style TargetType="Button">
<Setter Property="BackgroundColor" Value="#2A84D3" />
<Setter Property="BorderColor" Value="#1C5F9B" />
<Setter Property="BorderWidth" Value="3" />
<Setter Property="TextColor" Value="White" />
</Style>
定义样式
通常将样式定义为 ResourceDictionary 对象中的资源。使用资源字典可以轻松地在同一页面上的多个控件中使用样式,甚至可以在整个应用程序中使用该样式。下面的代码演示如何将样式定义为字典中的资源。请注意,使用 x:Key 属性为样式指定一个名称。通过命名样式,可以从 XAML 页中引用该样式。
<ContentPage.Resources>
<Style x:Key="MyButtonStyle" TargetType="Button">
...
</Style>
</ContentPage.Resources>
应用样式
通过将 named 分配给 Style 属性,可以将样式附加到控件。该赋值会导致将样式中的每个 Setter 对象应用于目标控件。下面的代码演示如何将一个按钮样式应用于两个按钮。
<Button Text="OK" Style="{StaticResource MyButtonStyle}" />
<Button Text="Cancel" Style="{StaticResource MyButtonStyle}" />
在前面的示例中,您使用了 StaticResource 标记扩展将样式附加到控件。当您不需要在运行时更改样式时,此技术非常有用。但是,如果要实现类似动态主题的内容,并且 UI 需要更改,该怎么办?在这种情况下,可以使用动态资源标记扩展来加载样式。
<Button Text="Cancel" Style="{DynamicResource MyButtonStyle}" />
动态资源侦听资源字典中 x:Key 属性的替换。如果编写代码将新样式加载到具有相同 x:Key 值的 ResourceDictionary 中,则新样式将自动应用于 UI。
对多个控件使用隐式样式
假设你的 UI 有 50 个按钮,并且你想要对所有按钮应用相同的样式。根据我们目前所知,您需要手动分配给每个按钮上的 Style 属性。这并不难做到,但仍然很乏味。
隐式样式是添加到资源字典而不为其提供 x:Key 标识符的样式。隐式样式将自动应用于指定 TargetType 对象的所有控件。
下面的代码演示上一个声明为隐式样式的示例。此样式将应用于页面上的每个按钮。
<ContentPage.Resources>
<Style TargetType="Button">
<Setter Property="BackgroundColor" Value="Blue" />
<Setter Property="BorderColor" Value="Navy" />
...
</Style>
</ContentPage.Resources>
重要
隐式样式与控件的匹配需要与指定的 TargetType 完全匹配。从目标类型继承的控件将不会接收样式。若要影响继承的控件,可以在定义样式时将 Style.ApplyToDerivedTypes 属性设置为 True。例如,若要将样式应用于 Button 类型并使其影响从 Button 继承的任何按钮(如 ImageButton、RadioButton 或您创建的自定义类型),可以使用如下所示的样式。
<ContentPage.Resources>
<Style TargetType="Button"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="Black" />
</Style>
</ContentPage.Resources>
覆盖样式
您可以将样式视为为控件提供一组默认值。如果现有样式接近您的要求,但包含一个或两个您不需要的 setter,则可以应用该样式,然后通过直接设置属性来覆盖值。显式设置在样式之后应用,因此它将覆盖样式中的值。
假设您要对页面上的几个按钮使用以下样式。
<Style x:Key="MyButtonStyle" TargetType="Button">
<Setter Property="BackgroundColor" Value="Blue" />
<Setter Property="BorderRadius" Value="10" />
<Setter Property="BorderWidth" Value="3" />
</Style>
此样式适用于除取消之外的所有按钮,取消需要红色背景。您可以对“取消”按钮使用相同的样式,只要您还直接设置了“背景颜色”属性即可。下面的代码演示如何重写颜色设置。
<Button
Text="Cancel"
Style="{StaticResource MyButtonStyle}"
BackgroundColor="Red"
... />
以祖先类型为目标
假设您希望为按钮和标签使用自定义背景色。您可以为每种类型的创建单独的样式,也可以创建一个将 TargetType 设置为 VisualElement 的样式。此技术之所以有效,是因为 VisualElement 是 Button 和 Label 的基类。
下面的代码演示一个样式,该样式面向应用于两种不同派生类型的基类。
<Style x:Key="MyVisualElementStyle" TargetType="VisualElement">
<Setter Property="BackgroundColor" Value="#2A84D3" />
</Style>
...
<Button Style="{StaticResource MyVisualElementStyle}" ... />
<Label Style="{StaticResource MyVisualElementStyle}" ... />
此示例使用 x:Key 标识样式,控件显式应用该样式。隐式样式在此处不起作用,因为隐式样式的 TargetType 必须与控件类型完全匹配。
使用 BasedOn 从样式继承
假设你想要为你的 UI 创建一个有凝聚力的外观。您决定所有控件都应使用一致的背景色。然后,背景色设置可能会以多种样式显示。下面的代码演示具有重复 setter 的两种样式。
<Style x:Key="MyButtonStyle" TargetType="Button">
<Setter Property="BackgroundColor" Value="Blue" />
<Setter Property="BorderColor" Value="Navy" />
<Setter Property="BorderWidth" Value="5" />
</Style>
<Style x:Key="MyEntryStyle" TargetType="Entry">
<Setter Property="BackgroundColor" Value="Blue" />
<Setter Property="TextColor" Value="White" />
</Style>
可以使用样式继承将重复的 setter 分解为基本样式。若要创建派生样式,请设置其 BasedOn 属性以引用基样式。新样式从其基本样式继承所有 setter。派生的样式还可以添加新的资源库,或将继承的 setter 替换为包含不同值的 setter。
下面的代码演示重构为层次结构的前面的示例样式。常见的二传手仅以基本样式出现,而不是重复。请注意,您使用静态资源标记扩展来查找基本样式。在这种情况下,不能使用动态资源。
<Style x:Key="MyVisualElementStyle" TargetType="VisualElement">
<Setter Property="BackgroundColor" Value="Blue" />
</Style>
<Style x:Key="MyButtonStyle" TargetType="Button" BasedOn="{StaticResource MyVisualElementStyle}">
<Setter Property="BorderColor" Value="Navy" />
<Setter Property="BorderWidth" Value="5" />
</Style>
<Style x:Key="MyEntryStyle" TargetType="Entry" BasedOn="{StaticResource MyVisualElementStyle}">
<Setter Property="TextColor" Value="White" />
</Style>
基本样式和派生样式的 TargetType 值必须兼容。要使样式兼容*,它们必须具有相同的 TargetType 属性,或者派生样式的 TargetType 是基本样式的 TargetType 的后代。
练习:创建和应用样式
在本练习中,您将在“提示计算器”应用程序中定义并应用页面级样式。
本练习是上一个练习的延续。使用现有解决方案作为这些步骤的起点,或在第一个练习中克隆的存储库的 exercise3/TipCalculator 文件夹中打开 TipCalculator 项目。
定义样式
让我们首先实现标签使用的“大小为 22 粗体”字体的样式。您将样式存储在页面级词典中。
在 TipCalculator 项目中,打开 StandardTipPage.xaml 文件。
将样式添加到页面的资源字典中,在现有资源之后。使用 infoLabelStyle 的 x:Key 值和 Label 的 TargetType 值。
<ContentPage.Resources>
<ResourceDictionary>
...
<Style x:Key="infoLabelStyle" TargetType="Label">
</Style>
</ResourceDictionary>
</ContentPage.Resources>
添加一个 Setter 实例,该实例将样式的 FontSize 属性设置为 fontSize 资源中的值。
添加另一个 Setter,将 FontAttributes 属性设置为粗体。
<Style x:Key="infoLabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="{StaticResource fontSize}" />
<Setter Property="FontAttributes" Value="Bold" />
</Style>
应用样式
找到使用 FontSize 值 {StaticResource fontSize} 和 FontAttributes 值 Bold 的三个 Label 控件。从标签中删除这些属性分配。
使用 StaticResource 标记扩展将 infoLabelStyle 样式分配给以下三个标签:
<!-- Left column = static labels -->
<Label x:Name="billLabel" Text="Bill" ... Style="{StaticResource infoLabelStyle}" ... />
<Label x:Name="tipLabel" Text="Tip" ... Style="{StaticResource infoLabelStyle}" ... />
<Label x:Name="totalLabel" Text="Total" ... Style="{StaticResource infoLabelStyle}" ... />
运行应用。该应用程序的外观应该与以前完全相同。但是,标签的字体属性现在使用样式进行设置。
更改字体样式
让我们来看看更新样式是多么容易。
- 返回到资源字典中的样式,并将 fontSize 资源更改为 32。
- 再次运行应用以查看更改。“账单”、“小费”和“总计”的三个标签应该更大。
- 将字体大小资源更改回 22。
创建基本样式
让我们通过添加基本样式来扩展标准提示页面的实现。您将定义一个新样式,其值与您在前面步骤中创建的样式重叠。您将在本练习的下一部分中重构此新样式。
打开 StandardTipPage.xaml 文件。
将另一个样式添加到页面的资源字典中。使用 baseLabelStyle 的 x:Key 值和 Label 的 TargetType 值。
重要
在 infoLabelStyle 样式上方定义此样式。当您从此样式继承时,此定位稍后将非常重要。一个样式只能继承自已在范围内的另一个样式。
添加设置 FontSize 属性的 Setter。请注意,这是对早期样式中的 setter 的重复。
<ContentPage.Resources>
<ResourceDictionary>
...
<Style x:Key="baseLabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="{StaticResource fontSize}" />
</Style>
...
<ResourceDictionary>
</ContentPage.Resources>
将新的 baseLabelStyle 应用于页面上显示小费和总计的计算金额的两个标签。从这些标签中删除显式的 FontSize 设置。下面的代码显示了一个示例。
<!-- Right column = user input and calculated-value output -->
...
<Label x:Name="tipOutput" Text="0.00" TextColor="Navy" Style="{StaticResource baseLabelStyle}" Grid.Row="1" Grid.Column="1" />
<Label x:Name="totalOutput" Text="0.00" TextColor="Navy" Style="{StaticResource baseLabelStyle}" Grid.Row="2" Grid.Column="1" />
运行应用程序。验证计算的“小费”和“总金额”(包含值 0.00)的值是否仍设置了正确的样式。
使用样式继承
现在,你已准备好使用继承重构样式。重构将允许您消除重复使用 setter。
打开 StandardTipPage.xaml 文件。
在页面的资源字典中找到 infoLabelStyle 样式。将此样式移到资源字典中的 baseLabelStyle 下方。
将 infoLabelStyle 样式的 BasedOn 属性设置为 baseLabelStyle。删除 FontSize 的 setter。您不再需要它,因为此样式现在从基本样式继承 FontSize 设置。
<ContentPage.Resources>
<ResourceDictionary>
...
<Style x:Key="baseLabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="{StaticResource fontSize}" />
</Style>
<Style x:Key="infoLabelStyle" BasedOn="{StaticResource baseLabelStyle}" TargetType="Label">
<Setter Property="FontAttributes" Value="Bold" />
</Style>
<ResourceDictionary>
</ContentPage.Resources>
备注
资源字典中资源的顺序很重要。baseLabelStyle 样式必须在引用它的任何其他样式之前定义,否则样式继承将不起作用。
运行应用并验证标签和计算量的样式是否仍具有正确样式。
创建和使用应用程序范围的资源
在 XAML 页上定义资源和样式是减少重复代码的好方法。不过,有一个问题。这些资源和样式仅在该特定 XAML 页上可用。页级资源字典不足以让您在有多个页面时避免跨应用程序重复编写代码。在本单元中,你将了解如何在应用程序中的所有页面共享资源和样式。
资源字典可用的位置
类定义资源属性。控件、页和布局继承自 VisualElement,因此它们都具有可以保存资源字典的 Resources 属性。
应用程序类还定义了资源属性。应用程序不从 VisualElement 继承,因此该属性被定义为此类的一部分。
下图显示了典型应用程序的结构。显示的每个元素都有一个资源属性,该属性可以保存资源字典。
备注
此图显示了对应用程序中项的组织方式的简化描述。在此图中,术语“视图”是指不充当任何子控件的容器的单一实例控件(如 Button 或 Label)。此外,术语“布局”还表示一个容器,该容器负责组织其子控件的布局。布局可以嵌套。例如,Grid 控件可以保存在 StackLayout 控件中。
如何定义应用程序级资源和样式
在与应用程序类关联的 XAML 文件中定义应用程序级资源和样式。下面的代码演示如何在应用程序资源字典中声明 Color 资源。
<Application.Resources>
<Color x:Key="MyTextColor">Blue</Color>
</Application.Resources>
.NET MAUI 如何定位资源或样式
假设您将资源应用于其中一个控件,如下面的代码所示。
<Label TextColor="{StaticResource MyTextColor}" ... />
.NET MAUI 需要找到该资源的定义,以便它可以应用该值。单个应用程序可以有多个字典。.NET MAUI 将搜索哪些词典,以哪种顺序搜索?为了回答这些问题,将页面上的 VisualElement 实例视为形成树状结构是有帮助的。应用程序位于根目录下,页面、布局和视图在其下方展开。此结构通常称为可视化树。树中的每个元素都可以有自己的字典,可以包含资源。.NET MAUI 中的样式搜索算法将引导到可视化树中:
- 使用应用资源的 VisualElement 实例中的字典开始搜索。在前面的示例中,搜索从 Label 类型开始。如果没有资源字典,或者它有字典但资源不存在,则搜索将继续。
- 移动到(控件)的父级并重复搜索。通常,要搜索的下一个位置是布局。
- 检查布局的父级。通常,要搜索的下一个位置是页面,但如果布局嵌套在另一个布局(例如 StackLayout 中的 Grid)中,则搜索将沿着树向上移动到父布局。
- 在字典中查找应用程序类。
搜索将返回找到的第一个具有匹配 x:Key 值的项目。下图总结了资源查找序列。
实际上,大多数开发人员会忽略视图和布局中的 Resources 属性。他们将页面级字典用于在单个页面上使用的内容。他们希望在多个页面之间共享的资源和样式是在应用程序级别定义的。然后,查找过程只需要检查两个字典:当前页面实例中的字典和应用程序中的字典。
备注
如果未找到具有指定键的资源,则应用将使用默认值进行样式设置。
重复的键
每个 ResourceDictionary 实例都是独立的,这意味着可以在多个字典中使用相同的 x:Key 值。在搜索路径上的多个字典中具有相同的 x:Key 标识符不会导致错误。与路径上第一个匹配的 x:Key 值关联的资源是将使用的资源。
例如,假设您在 Application 类中定义了以下资源:
<Application.Resources>
<x:String x:Key="msg">Two</x:String>
</Application.Resources>
然后,在 ContentPage 中定义以下资源,并将其应用于同一页面上的 Label:
<ContentPage.Resources>
<x:String x:Key="msg">One</x:String>
</ContentPage.Resources>
...
<Label Text="{StaticResource msg}">
由于使用了第一个匹配的 x:Key 值,因此 Text 属性将设置为 1。
练习:使用应用程序范围的资源
本练习的目标是通过将资源移动到 Tip Calculator 应用程序类中的资源字典,使资源在多个页面中可用。
本练习是上一个练习的延续。使用现有解决方案作为这些步骤的起点,或在第一个练习中克隆的存储库的 exercise4/TipCalculator 文件夹中打开 TipCalculator 项目。
验证页面级资源
让我们验证在一个页面上定义的资源在另一个页面上不可用。在本部分末尾,你的应用将无法正常运行。但是,您将在下一节中修复它。
在 TipCalculator 项目中,打开 CustomTipPage.xaml 文件。
将 infoLabelStyle 资源设置为 billLabel 标签的样式,并删除 FontSize 和 FontAttributes 属性的现有设置。
<Label x:Name="billLabel" Text="Bill" Style="{StaticResource infoLabelStyle}" Grid.Row="0" Grid.Column="0" />
运行应用。
选择“使用自定义计算器”以显示“自定义提示页”页。查看“帐单”标签。字体大小应小于其他标签,并且不以粗体显示。这是因为该页面尚未找到名为 infoLabelStyle 的资源(它位于其他页面的资源字典中),因此默认值用于字体大小和字体属性。
为应用程序级资源创建字典
让我们创建一个应用程序范围的资源字典,以保存要在多个页面上使用的资源。
打开 App.xaml 文件。请注意,此文件当前包含一个资源字典,其中包含一些现有的资源字典和样式,默认情况下,这些资源字典和样式用于 .NET MAUI 中内置的控件。若要查看默认情况下包含的所有样式,请查看 Resources/Styles.xaml 文件。
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TipCalculator"
x:Class="TipCalculator.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
打开 StandardTipPage.xaml 文件,并将 fontSize 资源以及 baseLabelStyle 和 infoLabelStyle 样式移动到 App.xaml 文件中的资源字典中。将它们放在现有样式之后,以便 App.Xaml 文件类似于以下示例:
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
...>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
<Color x:Key="bgColor">#C0C0C0</Color>
<Color x:Key="fgColor">#0000AD</Color>
<x:Double x:Key="fontSize">22</x:Double>
<Style x:Key="baseLabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="{StaticResource fontSize}" />
</Style>
<Style x:Key="infoLabelStyle" BasedOn="{StaticResource baseLabelStyle}" TargetType="Label">
<Setter Property="FontAttributes" Value="Bold" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
运行应用。
选择“使用自定义计算器”选项卡,并验证“帐单”标签现在是否设置了正确的样式。
总结
在本模块中,您了解了如何在 .NET MAUI 应用程序中使用资源和样式。目标是在企业品牌发生变化时简化 TipCalculator 应用的 UI 更新。你使用了以下功能来重新组织代码,以便快速准确地更新 UI:
- 资源:您为大小和颜色值创建了符号常量。
- 样式:通过使用一组属性值定义控件的外观。
资源和样式使你能够集中影响 UI 品牌的所有定义。这些功能使得在所有 UI 元素之间实现一致性变得更加容易。它们还允许您快速无误地进行更新。作为奖励,页面的 XAML 变得更易于阅读,因为您可以引用 Style 属性而不是单个属性设置。
更具体地说,在本模块中,您学习了如何:
- 在 MAUI XAML 用户界面中创建和使用静态资源
- 创建和使用动态资源
- 使用样式创建一致的用户界面
- 创建和使用应用程序范围的资源