设置XAML元素的FontSize和Background属性,就可以定义XAML元素的外观,如Button元素所示:
<Button Width="150" FontSize="12" Background="AliceBlue" Content="Click Me!"/>
除了定义每个元素的外观之外,还可以定义用资源存储的样式。为了完全定义控件的外观,可以使用模版,在把它们存储到资源中。
1. 样式
控件的Style属性可以赋予附带Setter的Style元素。Setter元素定义Property和Value属性,并给目标元素设置指定的属性和值。下例设置Background、FontSize、FontWeight和Margin属性。把Style设置为TargetType Button,以便直接访问Button的属性。
<Button Width="150" Content="Click Me!">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Button.Style>
</Button>
直接通过Button元素设置的Style对样式的共享没有什么帮助。样式可以放在资源中。在资源中,可以把样式赋予指定的元素,把一个样式赋予某一类型的所有元素,或者为该样式使用一个键。要把样式赋予某一类型的所有元素,可使用Style的TargetType属性。下面示例将样式赋予按钮,要定义需要引用的样式,必须设置x:Key:
<Page.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="LemonChiffon"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="5"/>
</Style>
<Style x:Key="ButtonStyle1" TargetType="Button">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Page.Resources>
在样例应用程序中,在页面内全局定义的样式在Page元素的Resources属性中指定。
在下面的XAML代码中,第一个按钮没有用元素属性定义样式,而是使用为Button类型定义的样式。对于下一个按钮,把Style属性用StaticResource标记扩展设置为{StaticResource ButtonStyle},而ButtonStyle指定了前面定义的样式资源的键,所以该按钮的背景为红色,前景是白色。
<Button Width="200" Content="Default Button Style" Margin="3"/>
<Button Width="200" Content="Named Style" Style="{StaticResource ButtonStyle1}"
Margin="3"/>
除了把按钮的Background设置为单个值之外,还可以将Background属性设置为定义了渐变色的LinearGradientBrush,如下所示:
<Style x:Key="FancyButtonStyle" TargetType="Button">
<Setter Property="FontSize" Value="22"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0.0" Color="LightCyan"/>
<GradientStop Offset="0.14" Color="Cyan"/>
<GradientStop Offset="0.7" Color="DarkCyan"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
本例中的下一个按钮的样式采用青色的线性渐变效果:
<Button Width="200" Content="Fancy Button Style" Style="{StaticResource FancyButtonStyle}"
Margin="3"/>
样式提供了一种集成方式。一个样式可以基于另一个样式。下面的AnotherButtonStyle样式基于FancyButtonStyle样式。它使用该样式定义的所有设置,且通过BasedOn属性引用,但Foreground属性除外,它设置为LinearGradientBrush:
<Style x:Key="AnotherButtonStyle" TargetType="Button" BasedOn="{StaticResource FancyButtonStyle}">
<Setter Property="Foreground">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Offset="0.2" Color="White"/>
<GradientStop Offset="0.5" Color="LightBlue"/>
<GradientStop Offset="0.9" Color="DeepSkyBlue"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
最后一个按钮应用了AnotherButtonStyle:
<Button Width="200" Content="Style inheritance" Style="{StaticResource AnotherButtonStyle}"
Margin="3"/>
下图显示了所有这些按钮样式化的效果。
2. 资源
从样式示例可以看出,样式通常存储在资源中。可以在资源中定义任何可共享的元素,前面为按钮的背景样式创建了画笔,它本身可以定义为一个资源,这样就可以在需要画笔的地方使用它。
下面的示例在StackPanel资源中定义一个LinearGradientBush,它的键名是MyGradientBrush。button1使用StackResource标记扩展将MyGradientBrush资源赋予Background属性:
<StackPanel x:Name="myContainer">
<StackPanel.Resources>
<LinearGradientBrush x:Key="MyGradientBrush" StartPoint="0,0" EndPoint="0.3,1">
<GradientStop Offset="0.0" Color="LightCyan"/>
<GradientStop Offset="0.14" Color="Cyan"/>
<GradientStop Offset="0.7" Color="DarkCyan"/>
</LinearGradientBrush>
</StackPanel.Resources>
<Button Width="200" Height="50" Foreground="White" Margin="5"
Background="{StaticResource MyGradientBrush}" Content="Click Me!"/>
</StackPanel>
</StackPanel>
这里,在StackPanel中定义资源。在上面的例子中,资源用Page或Windwos元素定义。基类FrameworkElement定义ResourceDictionary类型的Resource属性。这就是资源可以用派生自FrameworkElement的所有类(任意XAML元素)来定义的原因。
资源按层次结构来搜索。如果用根元素定义的资源,它就会应用于所有子元素。如果根元素是Grid,该Grid包含一个StackPanel,且资源是用StackPanel定义的,该资源就会应用于StackPanel中的所有控件。如果StackPanel包含一个按钮,但只用该按钮定义资源,这个样式就只对该按钮有效。
注意:
对于层次结构,需要注意是否为样式使用了没有Key的TargetType。如果用Canvas元素定义一个资源,并把样式的TargetType设置为应用于TextBox元素,该样式就会应用于Canvas中的所有TextBox元素。如果Canvas中有一个ListBox,该样式甚至会应用于ListBox包含的TextBox元素。
如果需要将同一个样式应用于多个窗口,就可以用应用程序定义样式。在用Visual Studio创建的Windows应用程序中,创建了App.xaml文件,以定义应用程序的全局资源。应用程序样式对其中的每个页面或窗口都有效。每个元素都可以访问用应用程序定义的资源。如果通过父窗口找不到资源,就可以通过Application继续搜索资源:
<Application
x:Class="StylesAndResources.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:StylesAndResources">
<Application.Resources>
</Application.Resources>
</Application>
3. 从代码中访问资源
要从后台代码中访问资源,基类FrameworkElement的Resource属性返回ResourceDictionary。该字典使用索引器和资源名称提供对资源的访问。可以使用COntainsKey方法检查资源是否可用。
下面看一个例子。按钮控件button1没有指定背景,但将Click事件动态赋予OnApplyResource()方法,以动态修改它:
<Button x:Name="button1" Width="200" Height="50" Margin="5"
Click="OnApplyResource" Content="Apply Resource Programatically"/>
使用WPF时,使用TryFindResource方法来迭代所有资源。使用UWP时,可以使用类似的方法,但是需要自己实现它。OnApplyResource方法调用扩展方法TryFindResource来查找名为MyGradientBrush的资源,并将其分配给控件的Background属性:
private void OnApplyResource(object sender, RoutedEventArgs e)
{
if (sender is Control control)
{
control.Background = control.TryFindResource("MyGradientBrush") as Brush;
}
}
方法TryFindResource使用ContainsKey检查请求的资源是否可用,它会递归地调用方法,以免资源还没有找到:
public static class FrameworkElementExtensions
{
public static object TryFindResource(this FrameworkElement e,string key)
{
if (e == null) throw new ArgumentNullException(nameof(e));
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(e));
if (e.Resources.ContainsKey(key))
{
return e.Resources[key];
}
else if (e.Parent is FrameworkElement parent)
{
return TryFindResource(parent,key);
}
else
{
return null;
}
}
}
4. 资源字典
如果相同的资源可用于不同的页面甚至不同的应用程序,把资源放在一个资源字典中就比较有效。使用资源字典,可以在多个应用程序之间共享文件,也可以把资源字典放在一个程序集中,共应用程序共享。
要共享程序集中的资源字典,应创建一个库。可以把资源字典文件(这里是Dictionary1.xaml)添加到程序集中。
Dictionary1.xaml定义了两个资源:一个是包含CyanGradientBrush键的LinearGradientBrush,另一个是用于按钮的样式,它可以通过PinkButtonStyle键来引用:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ResourcesLibUWP">
<LinearGradientBrush x:Key="CyanGradientBrush" StartPoint="0,0" EndPoint="0.3,1">
<GradientStop Offset="0.0" Color="LightCyan"/>
<GradientStop Offset="0.14" Color="Cyan"/>
<GradientStop Offset="0.7" Color="DarkCyan"/>
</LinearGradientBrush>
<Style x:Key="PinkButtonStyle" TargetType="Button">
<Setter Property="FontSize" Value="22"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0.0" Color="Pink"/>
<GradientStop Offset="0.3" Color="DeepPink"/>
<GradientStop Offset="0.9" Color="DarkOrchid"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
对于目标项目,需要引用这个库,并把资源字典添加到这个字典中。通过ResourceDictionary的MergedDictionaries属性,可以使用添加进来的多个资源字典文件。可以把一个资源字典列表添加到合并的字典中。对于UWP应用程序,引用的资源字典必须以ms-appx://模式作为前缀(现在可以省略该前缀):
<Application
x:Class="StylesAndResources.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:StylesAndResources">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResourcesLibUWP/Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
现在可以像本地资源那样使用引用程序集中的资源了:
<Button Width="300" Height="50" Style="{StaticResource PinkButtonStyle}"
Content="Referenced Resource"/>
5. 主题资源
WPF和Xamatin.Forms支持DynamicResource标记扩展,在应用程序运行时,如果资源发生变化,它会动态更新用户界面。尽管UWP应用程序不支持DynamicResource标记扩展,但这些应用程序也能动态改变样式。这个功能是基于主题的。通过主题,可以允许用户在Light和Dark主题之间切换(类似于可以用Visual Studio改变的主题)。
1. 定义主题资源
主题资源可以在资源字典的ThemeDictionaries中定义。在ThemeDictionaries中定义的ResourceDictionary对象需要分配一个包含主题名称(Light或Dark的键)。示例代码为浅色背景和暗色前景的Light主题定义了一个按钮,为浅色前景和暗色背景的Dark主题定义了一个按钮。用于样式的键在这两个字典中时一样的:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ResourcesLibUWP">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<Style x:Key="SampleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Style x:Key="SampleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
使用ThemeResource标记扩展可以指定样式。除了使用另一个标记扩展之外,其他的都与StaticResource标记扩展相同:
<Button Style="{ThemeResource SampleButtonStyle}" Content="Change Theme"
Click="OnChangedTheme"/>
根据选择的主题,使用相应的样式。
2. 选择主题
有不同的方式选择主题。首先,应用程序本身有一个默认的主题(也可以显示指定)。Application类的RequestedTheme属性定义了应用程序的默认主题。这在App.xaml内定义,在其中还引用了主题字典文件:
<Application
x:Class="StylesAndResources.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:StylesAndResources"
RequestedTheme="Dark">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResourcesLibUWP/Dictionary1.xaml"/>
<ResourceDictionary Source="ResourcesLibUWP/SampleThemes.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
RequestedTheme属性在XAML的元素层次结构中定义。每个元素可以覆盖用于它本身及其子元素的默认主题。下面的Grid元素改变了默认主题(Dark),指定为Light。现在它用于Grid元素及其所有子元素:
<Grid x:Name="grid1" RequestedTheme="Light">
<Button Style="{ThemeResource SampleButtonStyle}" Content="Change Theme"
Click="OnChangedTheme"/>
</Grid>
也可以在后台代码中通过设置RequestedTheme属性来动态更改主题:
private void OnChangedTheme(object sender, RoutedEventArgs e)
{
grid1.RequestedTheme = grid1.RequestedTheme == ElementTheme.Dark ?
ElementTheme.Light:
ElementTheme.Dark;
}
注意:
只有使用ThemeResource标记扩展,才可以动态加载相应主题。