到目前为止,您已经看到了开发应用程序的核心 WPF 构造块。您可以使用应用程序模型承载和提供主要由控件构成的应用程序内容。若要简化 UI 中的控件排列,并确保在窗口大小和显示设置更改时维护这种排列,请使用 WPF 布局系统。由于大多数应用程序都允许用户与数据交互,因此您可以使用数据绑定减少将 UI 与数据相集成的工作量。若要改进应用程序的视觉外观,请使用 WPF 提供的各种图形、动画和媒体支持。最后,如果您的应用程序通过文本和文档进行操作,您可以使用 WPF 文本、版式、文档、批注、打包和打印功能。
尽管如此,基本功能通常不足以创建和管理真正与众不同、具有视觉震撼力的用户体验。标准 WPF 控件可能不能与所需的应用程序外观集成。数据可能不能以最有效的方式显示。应用程序的整体用户体验可能不适合 Windows 主题的默认外观。在很多方面,演示技术需要具有像其他任何扩展性那样多的视觉扩展性。
出于这个原因,WPF 为创建独特的用户体验提供了各种机制,包括一个内容丰富的模型,用于控件、触发器、控件和数据模板、样式、UI 资源以及主题和外观。
内容模型
大多数 WPF 控件的主要目的都是为了显示内容。在 WPF 中,构成控件内容的项目类型和数量被称为控件的“内容模型”。有些控件只能包含一个项目和内容类型;例如,TextBox 的内容为字符串值,该值被分配给 Text 属性。下面的示例设置 TextBox 的内容。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.TextBoxContentWindow"
Title="TextBox Content">
...
<TextBox Text="This is the content of a TextBox." />
...
</Window>
下图演示了结果。
.png)
但是,其他控件可以包含多个具有不同内容类型的项目;Button 的内容(由 Content 属性指定)可以包含各种项目,包括布局控件、文本、图像和形状。下面的示例演示 Button,其内容包括 DockPanel、Label、Border 和 MediaElement。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.ButtonContentWindow"
Title="Button Content">
...
<Button Margin="20">
<!-- Button Content -->
<DockPanel Width="200" Height="180">
<Label DockPanel.Dock="Top" HorizontalAlignment="Center">Click Me!</Label>
<Border Background="Black" BorderBrush="Yellow" BorderThickness="2"
CornerRadius="2" Margin="5">
<MediaElement Source="media/wpf.wmv" Stretch="Fill" />
</Border>
</DockPanel>
</Button>
...
</Window>
下图演示了此按钮的内容。
.png)
有关各种控件支持的内容类型的更多信息,请参见 WPF 内容模型。
触发器
尽管 XAML 标记的主要目的是实现应用程序的外观,但您仍然可以使用 XAML 实现应用程序行为的某些方面。一个示例就是使用触发器根据用户交互更改应用程序的外观。有关更多信息,请参见样式设置和模板化中的“触发器”。
控件模板
WPF 控件的默认 UI 通常由其他控件和形状构造而来。例如,一个 Button 由 ButtonChrome 和 ContentPresenter 控件组成。ButtonChrome 提供标准按钮外观,而 ContentPresenter 显示按钮的内容(由 Content 属性指定)。
有时控件的默认外观可能与应用程序的整体外观不一致。在这种情况下,您可以使用 ControlTemplate 更改控件的 UI 的外观,而无需更改控件的内容和行为。
例如,下面的示例演示如何使用 ControlTemplate 更改 Button 的外观。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.ControlTemplateButtonWindow"
Title="Button with Control Template" Height="158" Width="290">
<!-- Button using an ellipse -->
<Button Content="Click Me!" Click="button_Click">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Margin="5">
<Ellipse Stroke="DarkBlue" StrokeThickness="2">
<Ellipse.Fill>
<RadialGradientBrush Center="0.3,0.2" RadiusX="0.5" RadiusY="0.5">
<GradientStop Color="Azure" Offset="0.1" />
<GradientStop Color="CornflowerBlue" Offset="1.1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<ContentPresenter Name="content" HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</Window>
Imports System.Windows ' Window, RoutedEventArgs, MessageBox
Namespace SDKSample
Public Class ControlTemplateButtonWindow
Inherits Window
Public Sub New()
InitializeComponent()
End Sub
Private Sub button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
MessageBox.Show("Hello, Windows Presentation Foundation!")
End Sub
End Class
End Namespace
using System.Windows; // Window, RoutedEventArgs, MessageBox
namespace SDKSample
{
public partial class ControlTemplateButtonWindow : Window
{
public ControlTemplateButtonWindow()
{
InitializeComponent();
}
void button_Click(object sender, RoutedEventArgs e)
{
// Show message box when button is clicked
MessageBox.Show("Hello, Windows Presentation Foundation!");
}
}
}
在此示例中,默认按钮 UI 被替换为 Ellipse,后者具有深蓝色的边框并使用 RadialGradientBrush 进行填充。ContentPresenter 控件显示 Button 的内容,即“Click Me!”。单击 Button 时,仍会作为 Button 控件的部分默认行为引发 Click 事件。下图演示了结果。
.png)
有关更多信息,请参见 ControlTemplate。有关介绍性示例,请参见使用 ControlTemplates 设置样式的示例。
数据模板
控件模板使您可以指定控件的外观,数据模板则允许您指定控件内容的外观。数据模板通常用于改进绑定数据的显示方式。下图演示 ListBox 的默认外观,它被绑定到一个 Task 对象集合,该集合中的每个任务都有一个名称、说明和优先级。
.png)
默认外观是您希望 ListBox 具有的外观。但是,每个任务的默认外观只包含任务名称。若要显示任务名称、说明和优先级,必须使用 DataTemplate 更改 ListBox 控件的绑定列表项的默认外观。下面的 XAML 定义了这样一个 DataTemplate,它通过使用 ItemTemplate 属性应用于每个任务。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.DataTemplateWindow"
Title="With a Data Template">
...
<!-- Data Template (applied to each bound task item in the task collection) -->
<DataTemplate x:Key="myTaskTemplate">
<Border Name="border" BorderBrush="DarkSlateBlue" BorderThickness="2"
CornerRadius="2" Padding="5" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Padding="0,0,5,0" Text="Task Name:"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=TaskName}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Padding="0,0,5,0" Text="Description:"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Padding="0,0,5,0" Text="Priority:"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Priority}"/>
</Grid>
</Border>
</DataTemplate>
...
<!-- Data template is specified by the ItemTemplate attribute -->
<ListBox
ItemsSource="{Binding}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"
Margin="5,0,5,5" />
...
</Window>
下图演示了此代码的效果。
.png)
请注意,ListBox 保留其行为和整体外观;只有列表框显示的内容的外观发生了变化。
有关更多信息,请参见数据模板化概述。有关介绍性示例,请参见数据模板化示例简介。
样式
开发人员和设计人员使用样式可以对其产品的特定外观进行标准化。WPF 提供了一个强大的样式模型,其基础是 Style 元素。下面的示例创建一个样式,该样式将窗口中的每个 Button 的背景色设置为 Orange。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.StyleWindow"
Title="Styles">
...
<!-- Style that will be applied to all buttons -->
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Orange" />
<Setter Property="BorderBrush" Value="Crimson" />
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Margin" Value="5" />
</Style>
...
<!-- This button will have the style applied to it -->
<Button>Click Me!</Button>
<!-- This labe will not have the style applied to it -->
<Label>Don't Click Me!</Label>
<!-- This button will have the style applied to it -->
<Button>Click Me!</Button>
...
</Window>
由于此样式针对所有 Button 控件,因此它自动应用于窗口中的所有按钮,如下图所示。
.png)
有关更多信息,请参见样式设置和模板化。有关介绍性示例,请参见介绍样式设置和模板化的示例。
资源
一个应用程序中的各控件应共享相同的外观,包括从字体和背景色到控件模板、数据模板和样式的所有方面。您可以使用 WPF 对 用户界面 (UI) 资源的支持将这些资源封装到一个位置,以便于重复使用。
下面的示例定义一个通用的由 Button 和 Label 共享的背景色。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.ResourcesWindow"
Title="Resources Window">
<!-- Define window-scoped background color resource -->
<Window.Resources>
<SolidColorBrush x:Key="defaultBackground" Color="Red" />
</Window.Resources>
...
<!-- Button background is defined by window-scoped resource -->
<Button Background="{StaticResource defaultBackground}">One Button</Button>
<!-- Label background is defined by window-scoped resource -->
<Label Background="{StaticResource defaultBackground}">One Label</Label>
...
</Window>
此示例使用 Window.Resources 属性元素实现背景色资源。此资源可用于 Window 的所有子项。资源范围有多种,包括下面按解析顺序列出的范围:
单个控件(使用继承的 FrameworkElement..::.Resources 属性)。
Window 或 Page(也使用继承的 FrameworkElement..::.Resources 属性)。
Application(使用 Application..::.Resources 属性)。
范围的多样性使您可以灵活选择定义和共享资源的方式。
作为将资源与特定范围直接关联的一个备用方法,您可以使用单独的 ResourceDictionary(可以在应用程序的其他部分引用)打包一个或多个资源。例如,下面的示例在资源字典中定义默认背景色。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Define background color resource -->
<SolidColorBrush x:Key="defaultBackground" Color="Red" />
<!-- Define other resources -->
...
</ResourceDictionary>
下面的示例引用上一个示例中定义的资源字典,从而在应用程序中共享。
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="BackgroundColorResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
...
</Application>
资源和资源字典是 WPF 支持主题和外观的基础。
有关更多信息,请参见资源概述。有关介绍性示例,请参见应用程序资源示例。
主题和外观
从视觉的角度上讲,主题定义 Windows 以及在其中运行的应用程序的整体外观。Windows 附带了几个主题。例如,Microsoft Windows XP 附带了 Windows XP 和 Windows 经典主题,而 Windows Vista 附带了 Windows Vista 和 Windows 经典主题。由主题定义的外观可定义 WPF 应用程序的默认外观。但是,WPF 并未与 Windows 主题直接集成。由于 WPF 的外观由模板定义,因此 WPF 为每个已知 Windows 主题包括了一个模板,这些主题包括 Aero (Windows Vista)、Classic (Microsoft Windows 2000)、Luna (Microsoft Windows XP) 和 Royale (Microsoft Windows XP Media Center Edition 2005)。这些主题作为资源字典进行打包,如果未在应用程序中找到资源,则可以解析这些资源字典。许多应用程序依赖于这些主题来定义其视觉外观;与 Windows 外观保持一致有助于用户更轻松地熟悉更多应用程序。
另一方面,某些应用程序的用户体验不一定来自标准主题。例如,Microsoft Windows Media Player 通过音频和视频数据进行操作,并受益于不同风格的用户体验。此类 UI 可以提供自定义的、应用程序特定的主题。这些主题称为外观,带有外观的应用程序通常提供挂钩,用户可以通过这些挂钩自定义外观的各个方面。Microsoft Windows Media Player 具有多个预制的外观以及大量第三方外观。
WPF 中的主题和外观都可以使用资源字典非常轻松地进行定义。下面的示例演示示例外观定义。
<!-- Blue Skin -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SDKSample">
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue" />
</Style>
...
</ResourceDictionary>
<!-- Blue Skin -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SDKSample">
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue" />
</Style>
...
</ResourceDictionary>
<!-- Yellow Skin -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SDKSample">
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Yellow" />
</Style>
...
</ResourceDictionary>
<!-- Yellow Skin -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SDKSample">
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Yellow" />
</Style>
...
</ResourceDictionary>
有关更多信息,请参见样式设置和模板化中的“共享的资源和主题”。有关介绍性示例,请参见带有外观的应用程序示例。
自定义控件
尽管 WPF 提供了大量自定义项支持,您仍然可能会遇到现有 WPF 控件不能满足应用程序或用户需求的情况。在以下情况下可能会出现这种情形:
但是,目前您可以利用三个 WPF 模型之一创建一个新的控件。每个模型都针对一个特定的方案,并要求您的自定义控件从特定的 WPF 基类派生而来。此处列出了此三个模型:
用户控件模型。从 UserControl 派生的自定义控件,由其他一个或多个控件组成。
控制模型。从 Control 派生的自定义控件,用于生成使用模板将其行为和外观相分离的实现,与多数 WPF 控件非常相似。从 Control 派生使您可以比用户控件更自由地创建自定义 UI,但可能需要投入更多精力。
框架元素模型。从 FrameworkElement 派生的自定义控件,其外观由自定义呈现逻辑(而不是模板)定义。
下面的示例演示从 UserControl 派生的自定义数值 up/down 控件。
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.NumericUpDown">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Value text box -->
<Border BorderThickness="1" BorderBrush="Gray" Margin="2" Grid.RowSpan="2"
VerticalAlignment="Center" HorizontalAlignment="Stretch">
<TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
<!-- Up/Down buttons -->
<RepeatButton Name="upButton" Click="upButton_Click" Grid.Column="1"
Grid.Row="0">Up</RepeatButton>
<RepeatButton Name="downButton" Click="downButton_Click" Grid.Column="1"
Grid.Row="1">Down</RepeatButton>
</Grid>
</UserControl>
imports System 'EventArgs
imports System.Windows 'DependencyObject, DependencyPropertyChangedEventArgs,
' FrameworkPropertyMetadata, PropertyChangedCallback,
' RoutedPropertyChangedEventArgs
imports System.Windows.Controls 'UserControl
Namespace SDKSample
' Interaction logic for NumericUpDown.xaml
Partial Public Class NumericUpDown
Inherits System.Windows.Controls.UserControl
'NumericUpDown user control implementation
...
End Class
End Namespace
using System; // EventArgs
using System.Windows; // DependencyObject, DependencyPropertyChangedEventArgs,
// FrameworkPropertyMetadata, PropertyChangedCallback,
// RoutedPropertyChangedEventArgs
using System.Windows.Controls; // UserControl
namespace SDKSample
{
public partial class NumericUpDown : UserControl
{
// NumericUpDown user control implementation
...
}
}
下一个示例演示将用户控件与 Window 合并所需的 XAML。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.UserControlWindow"
xmlns:local="clr-namespace:SDKSample"
Title="User Control Window">
...
<!-- Numeric Up/Down user control -->
<local:NumericUpDown />
...
</Window>
下图演示 Window 中承载的 NumericUpDown 控件。
.png)
有关自定义控件的更多信息,请参见控件创作概述。有关介绍性示例,请参见控件自定义示例。