WPF 导览 - 第五部分 (样式)






4.92/5 (101投票s)
Windows Presentation Foundation 的导览,一次介绍一个功能。
目录
- 第 1 部分 (XAML):了解 XAML 及其在 WPF 应用程序中的使用方式。
- 第 2 部分 (布局):了解布局面板及其如何用于构建用户界面。
- 第 3 部分 (数据绑定):了解 WPF 数据绑定如何工作以及如何使用。
- 第四部分 (数据模板和触发器):了解数据模板和触发器是如何工作的以及如何使用它们。
- 第 5 部分 (样式):了解 WPF 中 UI 的样式设置方法。
引言
这是关于 Windows Presentation Foundation 的介绍系列文章中的第五篇。在上一篇文章中,我们研究了数据模板和触发器,以了解如何在 XAML 中描述数据对象的渲染。在本文中,我们将研究样式,以及它们如何在 WPF 赛马演示应用程序中使用(该应用程序可在本系列第一篇文章的顶部下载)。
与本系列的其他文章一样,本文不会详尽地涵盖其主题。相反,我们将研究足够的基础知识,以便了解这些功能如何在演示应用程序中使用。如果您想了解更多关于如何使用样式的知识,请参阅“外部链接”部分获取更多信息。
背景
WPF 借鉴了 Web 和桌面编程世界的许多概念。用户界面布局与行为的分离(即 XAML 与代码隐藏)是对 ASP.NET 的致敬。用于精细控制用户交互的大量事件驱动 API 让人联想到 Windows Forms 编程模型。WPF 可以被认为是各种 UI 平台中最佳功能的提炼,并加上了广泛的新功能。
Web 开发界对 WPF 的一项重要贡献是样式概念。WPF 中的样式设置在某种程度上类似于 Web 开发者使用的层叠样式表 (CSS)。WPF 中样式设置的基本思想是,您可以定义一个 Style
对象,该对象应用于 UI 中特定类型的元素,例如 Button
或 TextBox
。该样式可以应用于该元素类型的每个实例,或者您可以选择性地将其应用于某些实例。WPF 中的样式设置实际上归结为一种方便的方式,可以为一或多个视觉元素应用属性设置。
样式 vs. 主题
在开始研究样式之前,区分样式和主题非常重要。操作系统有“主题”,WPF 应用程序有“样式”。主题是操作系统级别的概念:例如 Windows XP 中看到的蓝色、绿色和银色主题,或者 Vista 中的 Aero 主题。WPF 应用程序会自动检测当前操作系统主题并在可能的情况下使用该配色方案,甚至可以以编程方式选择要使用的主题。
样式仅存在于一个 WPF 应用程序中,或者仅存在于一个 WPF 应用程序的一个窗口中,甚至只存在于一个窗口的一个部分。样式不能应用于存在它的 WPF 应用程序之外的任何内容,也不能应用于在 WPF 窗口中托管的任何 WinForms 控件。
有关 WPF 中主题支持的更多信息,请参阅本文末尾的“外部链接”部分。
Style 类
WPF 中的整个样式基础结构都基于 Style 类。它拥有相对较少的公共成员,这使得理解样式的工作原理变得容易。Style
类的实例几乎总是用 XAML 创建,但如果需要,也可以在代码中创建。
以下是将在稍后使用的 Style
的一些属性
- Resources – 是一个
ResourceDictionary
,您可以在其中放置仅在Style
中使用的对象,例如画笔、值转换器等。 - Setters – 是
Setter
和EventSetter
对象的集合,用于应用值给属性,或将处理程序分配给事件。这是Style
类的内容属性,因此在 XAML 中使用起来非常方便。 - TargetType – 指示
Style
将应用于哪种类型的元素,例如TreeView
或Button
。
WPF 赛马演示应用程序中创建的样式非常简单。Style
类还有其他一些常用属性,但它们并未在本例中使用,例如:
- BasedOn – 允许样式继承。您可以从另一个
Style
派生一个Style
,以在不重复复制核心Style
的 XAML 的情况下创建自定义。 - Triggers – 就像本系列上一篇文章中看到的
DataTemplate
一样,此属性包含一组触发器,可用于有条件地将值应用于属性。
Style
可以应用于任何派生自 FrameworkElement (FrameworkElement) 或 FrameworkContentElement 的对象,这两个类都公开一个名为 Style
的公共属性。
没有样式
如果 WPF 不提供样式设置元素的方法,那么您将不得不疯狂地考虑为应用程序的用户界面创建独特、品牌化的外观和感觉。在整个应用程序的窗口中创建视觉一致性将很快成为维护的噩梦,尤其是在需要更改视觉样式的某些方面时。
例如,考虑以下 XAML
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<TextBlock Background="Tan" Margin="2,4">Bike</TextBlock>
<TextBlock Background="Tan" Margin="2,4">Car</TextBlock>
<TextBlock Background="Tan" Margin="2,4">Truck</TextBlock>
</StackPanel>
</Border>
简单的标记会生成如下所示的内容:
假设我们正在构建的应用程序过去有一个规则,即 TextBlock
必须始终具有米色背景颜色,但有一天,我们想象的公司里的一位大人物决定米色不再是好颜色。相反,现在所有 TextBlock
都应该具有浅灰色背景颜色。
如果我们的应用程序只包含上面代码片段中看到的三个 TextBlock
,那不会太麻烦。我们只需更新这三个属性设置:
<Border BorderBrush="Black" BorderThickness="2" Margin="10">
<StackPanel>
<TextBlock Background="LightGray" Margin="2,4">Bike</TextBlock>
<TextBlock Background="LightGray" Margin="2,4">Car</TextBlock>
<TextBlock Background="LightGray" Margin="2,4">Truck</TextBlock>
</StackPanel>
</Border>
但是,如果我们的应用程序恰好包含数百甚至数千个 TextBlock
,那么我们就麻烦了。突然之间,我们会希望有一种简单的方法可以设置每个 TextBlock
的 Background
属性为浅灰色。
有了样式
幸运的是,有一种简单的方法可以设置任意数量元素的属性:“样式”。让我们看看如何使用 Style
来解决上一节中描述的问题。这是其中的一种方法:
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<StackPanel.Resources>
<Style x:Key="TxtBlkStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Margin" Value="2,4" />
</Style>
</StackPanel.Resources>
<TextBlock Style="{StaticResource TxtBlkStyle}">Bike</TextBlock>
<TextBlock Style="{StaticResource TxtBlkStyle}">Car</TextBlock>
<TextBlock Style="{StaticResource TxtBlkStyle}">Truck</TextBlock>
</StackPanel>
</Border>
上面的 XAML 在 StackPanel
的 Resources
集合中创建了一个 Style
。该 Style
以 TextBlock
元素类型为目标,并设置其 Background
和 Margin
属性。请注意,Style
有一个键,即 'TxtBlkStyle
'。然后,每个 TextBlock
上的 Style
属性被显式设置为引用该 Style
。其结果是以下 UI:
实际上,还有一种更简单的方法可以完成此任务。如果您不给 Style
指定键,它将自动应用于类型与 Style
的 TargetType
匹配的所有元素。下面是一个示例:
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Margin" Value="2,4" />
</Style>
</StackPanel.Resources>
<TextBlock>Bike</TextBlock>
<TextBlock>Car</TextBlock>
<TextBlock>Truck</TextBlock>
</StackPanel>
</Border>
到目前为止看到的解决方案并没有真正解决总体问题。如果我们希望整个应用程序都包含浅灰色 TextBlock
,那么我们需要将我们的 Style
移动到资源树的更高级别,例如 Application
的资源(在此处和在此处阅读更多内容)。这样,应用程序中的所有 TextBlock
都可以使用我们创建的 Style
。这是一个使用此技术的示例:
<!-- App.xaml -->
<Application x:Class="WPF_Test.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml"
>
<Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Margin" Value="2,4" />
</Style>
</Application.Resources>
</Application>
<!--Somewhere inside of SomeWindow.xaml -->
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel>
<TextBlock>Bike</TextBlock>
<TextBlock>Car</TextBlock>
<TextBlock>Truck</TextBlock>
</StackPanel>
</Border>
由于 Application
的资源集合是放置 Style
的最显眼的位置,因此应用程序中的所有窗口中的所有 TextBlock
都将使用该 Style
。可以在任何窗口或窗口的元素树中的任何位置重写该 Style
,但默认情况下,应用程序中的所有 TextBlock
都将使用该 Style
。
WPF 赛马应用程序如何使用样式
WPF 赛马演示应用程序创建了两个 Style
。其中一个用于影响上一篇文章中介绍的 RaceHorse
类的数据模板中的 Border
元素。另一个为应用程序“命令条”区域(位于窗口底部附近)中的 TextBlock
提供通用属性值。
赛道边框样式
如果 RaceHorse
数据模板的根 Border
元素没有应用 Style
,UI 会是这样的:
应用 Style
后,它看起来像这样:
在 RacePitBorderStyle.xaml 文件中,您会找到一个 ResourceDictionary
,其中包含一个键为 'RacePitBorderStyle
' 的 Style
。该文件包含以下 XAML:
<!-- This resource dictionary contains the Style applied to
each horse's race pit. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Style x:Key="RacePitBorderStyle" TargetType="Border">
<Style.Resources>
<LinearGradientBrush x:Key="BackBrush"
StartPoint="0.5,0" EndPoint="0.5,1"
>
<GradientStop Color="#88000000" Offset="0.1" />
<GradientStop Color="#CC000000" Offset="0.9" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="BorderBrush"
StartPoint="0.5,0" EndPoint="0.5,1"
>
<GradientStop Color="#18000000" Offset="0.1" />
<GradientStop Color="#08000000" Offset="0.9" />
</LinearGradientBrush>
</Style.Resources>
<Setter Property="Background" Value="{StaticResource BackBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Margin" Value="2,4" />
</Style>
</ResourceDictionary>
该 Style
应用于 RaceHorse
数据模板中的根 Border
元素,如下所示:
<!-- RaceHorseDataTemplate.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfHorseRace"
>
<!-- Import the resource dictionary which contains the Style
applied to Border of each horse's "pit". -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="RacePitBorderStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type local:RaceHorse}">
<Border x:Name="racePit" Style="{StaticResource RacePitBorderStyle}">
<!-- Other elements omitted for clarity. -->
</Border>
</DataTemplate>
</ResourceDictionary>
命令条文本样式
演示应用程序中使用的另一个 Style
是隐式应用于其目标元素的。此 Style
可以在 Window1.xaml 文件中,“命令条”部分的 XAML 中看到。
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<!-- This Style is applied to all TextBlock
elements in the command strip area. -->
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="#EE000000" />
</Style>
</StackPanel.Resources>
<TextBlock>Rotation: </TextBlock>
<Slider ... />
<TextBlock ... />
<TextBlock> degrees</TextBlock>
</StackPanel>
请注意,此 Style
的 TargetType
设置为“TextBlock
”。当 TargetType
设置为 WPF 框架的一部分类型时,您不必使用本文前面看到的更繁琐的 {x:Type TypeName}
语法。由于上面的 Style
没有键,因此它会自动应用于 StackPanel
中的所有 TextBlock
元素。
外部链接
样式和主题
- WPF 中的默认模板 – 强烈推荐
样式
主题
- 创建和应用自定义主题
- Windows Presentation Foundation 中的主题支持
- Vista 外观在非 Aero 主题上的体现
- Microsoft.Windows.Themes 命名空间
历史
- 2007 年 4 月 12 日 - 创建文章。