6. 样式和资源

设置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标记扩展,才可以动态加载相应主题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值