简单的样式修改

TIP

本章涉及知识点:

  • 样式的添加
  • 样式的复用
  • 样式的继承

视频地址:https://www.bilibili.com/video/av53560120/

前言

前面的课程中,我们学习了一些简单的布局知识。通过布局,可以有效地组织我们呈现的信息,让软件界面井井有条。

但倘若将软件与人作比,良好的布局就像是一副整齐的骨骼,没多少人会觉得骨头架子很好看。若要美观,当然还需要漂亮的衣裳。

在软件中,所谓“漂亮的衣裳”指的就是样式。这里说的样式,包括了界面的背景,文本的颜色、字体、字号,控件的外观等等设计层面的东西。

听上去很繁杂,事实上也的确是。不过归根结底都会落到一个点,那就是资源。本章所谈的样式,不过是更高一级对于资源的整合罢了。

不过我们却并不从资源出发,这并不易于理解。先让我们动手修改一个按钮的样式吧,这样更有成就感。

一个蓝色的按钮

新建一个UWP的项目,然后在 MainPage 中新建一个<Button>,里面随便写一点内容。

如你所见,生成了一个带有默认样式的按钮。对于我们的软件来说,大多数情况下灰色的按钮都不符合我们的主题颜色。假设我们现在以蓝色为主色调,这个按钮也要变成蓝色才行。

修改按钮的背景色,我们可以用Background属性。这个属性大家应该不陌生,在之前的课程中,尽管没有刻意介绍,但我们都是使用过的。

我们将Background的属性值设置为Blue。如你所见,现在按钮的背景色变成蓝色了。很简单,对吗?

在这种蓝色下,黑色的文本就不太好看,我们可以尝试用白色来凸显文本的内容。

设置文本的颜色,我们一般用Foreground属性。这和CSS中的color属性不同。Foreground,中文称之为前景色,乍看之下与文本似乎并没有什么关系,但我可以很明确地告诉你,在实际开发中,你可以将Foreground视作文本颜色的代指,因为绝大多数情况下Foreground都是文本颜色。

我们将Foreground设置为White,OK,这样一个有着自己颜色的,被我们定制过的按钮就出现了。

这是我们修改了默认样式的按钮:

<Button Background="Black"
        Foreground="White" />

但就是这普通的样式修改,也有很多值得说道的地方。

样式的复用

让我来举一个情景吧。

你有一款以蓝色为主题色的软件,那么所有的主要功能按钮,比如说确认按钮,添加按钮等,都是蓝色的,而且它们都有着同样的字体,同样的字号,这很正常,对吧。这些按钮有的在主界面,有的在侧边栏,有的在对话框的底部。

很显然,这些按钮都经历了样式修改。问题在于,修改的方式是怎样的?

我们当然可以直接写在控件里面,但是这意味着每一个按钮你都要写一次,记住每一个样式的具体属性值,你比如说,字体是微软雅黑,字号是14,背景色不是普通的Blue,而是一个以Hash表示的16进制颜色代码。说记,按钮一多肯定记不住,那你就要来回且页面,找一个原始控件作为参照。而当你写完了所有的按钮,最后发现字号小了,需要再调整,你又要回过头一个个改……

不用试,光想想就知道这简直是噩梦。

对应这种情景,XAML也有自己的解决办法。

其实你只要把写C#的经验套用过来就可以。对于C#代码,我们会把重复的逻辑提取出来作为一个函数加以引用。这样不光节省时间,而且对于代码的健壮性也是大有好处。

同样,对于重复的样式我们也可以提取出来,命个名,然后在控件中直接引用即可。

让我们来看看如何提取公用样式以及它的语法是怎样的。

以我们之前写的蓝色按钮为例。在编写公用样式的时候,我们可以写成下面这个样子:

<Style TargetType="Button" x:Key="BlueButton">
    <Setter Property="Background" Value="Blue" />
    <Setter Property="Foreground" Value="White" />
</Style>

这里我们还是拿CSS来比较,如果你要写一个按钮的样式,你可以写成下面这个样子:

button.blue-button{
    background: blue;
    color: white;
}

即便你不写前面的button,只要写类名或者ID名即可,CSS从这一点上来说要自由很多,你写了类,不管你绑定到什么元素上面都可以。

但是对于XAML来说就不可以了。我们之前讲过,XAML本质上就是类,类和属性的关系不用我再多说。两个没有继承关系的类,即便其中某一个属性名和属性类型相同,他俩也是不可互相赋值的。也就是说,我们在提到Background的时候必须要说清楚,是谁的Background。

这也就是XAML在写公共样式的时候必须声明TargetType的原因。如果你想要让这个样式跨控件应用,比如说又能被Button引用,又能被TextBlock引用,那么TargetType就要写这两个控件的基类,我们可以写FrameworkElement,当然你还可以在往前追溯,但那没有意义。因为当你的TargetType确定后,Style里的属性范围也就确定了,再往前追溯那就不是最大公约数了,你想要的属性也可能消失了。

当我们敲定了TargetType之后,我们要给这个公共样式命名,这样才能去引用,这和我们给控件起名是一样的道理。

之后,我们在Style中写Setter,一个Setter表示一个属性,通过Property标识属性名,通过Value标识属性值。

这样一个个把你想要的属性写下来,就算完成了一个公共样式的创建。

从这里可以看出,在样式的编写上,XAML其实比CSS要繁琐一些。如果你是从前端过来的,你可能有些不适应,但是我告诉你这是值得的,一切为了类型安全嘛。

接下来的问题就是,这个公共样式放在哪呢?

在<Page>内部,<Grid>的上方,新建一个附加属性标签<Page.Resource>,在这里面把我们的公共样式放进来。

<Page.Resource>
    <Style TargetType="Button" x:Key="BlueButton">
        <Setter Property="Background" Value="Blue" />
        <Setter Property="Foreground" Value="White" />
    </Style>
</Page.Resource>

这时候,删除按钮中的样式属性,添加一个对公共样式的引用:

<Button Style="{StaticResource BlueButton}" />

没问题,一切正常,这说明样式已经被应用到按钮上了。这样在新建多个按钮控件时,就可以通过引用的方式来设置样式,而且想要修改样式,只用修改公共样式的定义就可以了。这样无疑大大简化了我们的工作。

样式的继承

让我们丰富一下我们的应用情景。在一个应用之中,总会有一些危险操作,比如说删除,比如说退出。这些也都是按钮,它们和主要按钮在样式上的区别也仅仅只是颜色不同,比如我们用红色来标识危险按钮。

现在我们有两条路。

  1. 样式覆盖。

我们先引用主要按钮的样式,然后在它的后面重写Background属性,这样后写的属性会把公共样式中的属性覆盖掉,我们就实现了保留按钮大部分外观的情况下,只改变颜色的目的。

  1. 写新的公共样式

如果你之前认真听我讲了的话,我相信你不会选择第一种方式,因为这样确实弊端颇多。那么摆在面前的就是另外一条路了,写一个新的RedButton的公共样式。

但是这样感觉很亏。

为什么呢?

因为除了背景色,这俩按钮的其它属性都是一样的,它们有着相同的字体,相同的字号,相同的文本颜色,甚至其它更多的样式属性。仅仅为了改一个背景色,就要把所有的属性再写一遍,这实在太浪费了。

还记得之前我们为什么要写公共样式吗?就是为了把重复的样式提取出来。

那么已经是公共样式的样式还能再提取吗?换句话说,控件可以引用样式,那么样式本身可以引用别的样式吗?

可以。

怎么做?

通过继承。

既然我们的两个按钮除了背景色,其余属性全部相同,那么我们就把所有相同的属性提取出来,做一个单独的样式:

<Style TargetType="Button" x:Key="BasicButton">
    <Setter Property="Foreground" Value="White" />
</Style>

TIP

这里为了简单起见,只写了Foreground一种属性,事实上还可以包括字体、字号等其它自定义属性。

之后,我们通过BasedOn属性来让我们的两个按钮样式继承BasicButton样式。

<Style TargetType="Button" BasedOn="{StaticResource BasicButton}" x:Key="BlueButton">
    <Setter Property="Background" Value="Blue" />
</Style>

<Style TargetType="Button" BasedOn="{StaticResource BasicButton}" x:Key="RedButton">
    <Setter Property="Background" Value="Red" />
</Style>

现在尝试新建两个按钮,然后分别引用 BlueButtonRedButton 看看效果吧!


小结

创建公用样式目的何在?

相信通过刚才的举例你已经很清楚了。公用样式存在的目的主要有两个:

  1. 提高代码可维护性,着重体现在代码修改上。
  2. 增强UI的一致性。

但是我们这样创建的Style是非常基础的,之前的例子,看上去很简单,但实际上,通过这种方式定制的样式只是表面功夫。

我们知道,按钮的外观是会随着状态变化而变化的,在普通状态下是一个样,在鼠标滑过的时候是一个样,在点击的时候又是一个样,甚至在按钮禁用的时候还有一种样式。普普通通地设置一个背景色,一个前景色,只会改变按钮在普通状态下的外观,对于其它状态的样式并没有任何帮助。

如何更改控件在不同状态下的样式,这涉及到控件模板,但这不是我们这节课的内容。这里只是为了让你了解,这种普通样式的局限性。

但你要说它就无用武之地了吗?也不尽然。

事实上还有许多无状态的控件。比如TextBlock,比如ProgressRing等等。在你以后学习自定义控件时,这种样式也可以帮到你。

这是后话了,只有在自己开发的时候你才会有更深刻的理解。