五、样式和模板
5.1 样式基础
样式是一种用于集中设置 UI 元素属性的机制,可以将一组属性应用到多个元素上,实现统一的外观风格。样式可以定义在资源字典中,也可以直接在 XAML 文件中定义。
5.1 样式基础
<Window.Resources>
<Style x:Key="MyButtonStyle" TargetType="Button">
<Setter Property="Background" Value="LightGreen"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Padding" Value="10"/>
</Style>
</Window.Resources>
<Button Style="{StaticResource MyButtonStyle}" Content="Styled Button"/>
在这个例子中,我们在 Window.Resources
里定义了一个名为 MyButtonStyle
的样式,其目标类型是 Button
。通过 Setter
元素,我们设置了按钮的背景色、前景色、字体大小和内边距。然后将这个样式应用到一个按钮上,使得该按钮具有我们所定义的外观。
样式继承
样式可以继承其他样式的属性,通过 BasedOn
属性来实现。例如:
<Window.Resources>
<Style x:Key="BaseButtonStyle" TargetType="Button">
<Setter Property="Background" Value="LightBlue"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style x:Key="DerivedButtonStyle" TargetType="Button" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Resources>
<Button Style="{StaticResource DerivedButtonStyle}" Content="Inherited Styled Button"/>
这里 DerivedButtonStyle
继承了 BaseButtonStyle
的属性,同时又添加了自己的 FontSize
属性。这样可以提高样式的复用性,避免重复定义相同的属性。
5.2 隐式样式
隐式样式是一种不指定 x:Key
的样式,它会自动应用到所有指定 TargetType
的元素上。例如:
<Window.Resources>
<Style TargetType="TextBox">
<Setter Property="Background" Value="LightYellow"/>
<Setter Property="BorderBrush" Value="Gray"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
</Window.Resources>
<TextBox Text="Implicit Styled TextBox"/>
在这个窗口中的所有 TextBox
都会自动应用这个样式,无需显式地指定样式。
5.3 模板基础
5.3.1 ControlTemplate
ControlTemplate
用于自定义控件的外观。它可以完全改变控件的可视化结构,而不仅仅是设置属性。例如,自定义一个按钮的模板:
<Window.Resources>
<ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button">
<Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="LightGray"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="DarkGray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Button Template="{StaticResource CustomButtonTemplate}" Content="Custom Button"/>
在这个模板中,我们使用 Border
元素作为按钮的外层容器,通过 TemplateBinding
将 Border
的属性绑定到按钮的属性上。ContentPresenter
用于显示按钮的内容。ControlTemplate.Triggers
部分定义了触发条件,当鼠标悬停或按钮被按下时,改变 Border
的背景色。
5.3.2 DataTemplate
DataTemplate
用于定义数据项的呈现方式。当我们将一个集合绑定到一个列表控件(如 ListView
、ComboBox
等)时,DataTemplate
可以决定每个数据项在界面上的显示形式。例如:
<Window.Resources>
<DataTemplate x:Key="PersonDataTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="5"/>
<TextBlock Text="{Binding Age}" Margin="5"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Persons}" ItemTemplate="{StaticResource PersonDataTemplate}"/>
假设 Persons
是一个包含 Person
对象的集合,每个 Person
对象有 Name
和 Age
属性。PersonDataTemplate
定义了每个 Person
对象在 ListBox
中的显示方式,即使用一个水平的 StackPanel
显示姓名和年龄。
5.4 资源字典
资源字典是一种用于集中管理样式、模板、画笔等资源的机制。可以将资源字典定义在单独的 .xaml
文件中,然后在多个 XAML 文件中引用。
创建资源字典文件
创建一个名为 MyResources.xaml
的文件,内容如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="SharedButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Orange"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<DataTemplate x:Key="SharedDataTemplate">
<TextBlock Text="{Binding SomeProperty}" Foreground="Red"/>
</DataTemplate>
</ResourceDictionary>
在 XAML 文件中引用资源字典
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Button Style="{StaticResource SharedButtonStyle}" Content="Shared Styled Button"/>
<ListBox ItemsSource="{Binding SomeCollection}" ItemTemplate="{StaticResource SharedDataTemplate}"/>
通过这种方式,我们可以在多个窗口或控件中共享资源,提高代码的可维护性和复用性。
六、动画和多媒体
6.1 动画基础
WPF 的动画系统允许开发者对 UI 元素的属性进行动态变化,从而创建出各种生动的效果。动画主要分为线性动画、关键帧动画和路径动画。
6.1.1 线性动画
线性动画是最简单的动画类型,它在指定的时间内从一个值线性变化到另一个值。例如,实现一个按钮在点击时逐渐变大的动画:
<Window.Resources>
<Storyboard x:Key="GrowButtonAnimation">
<DoubleAnimation Storyboard.TargetProperty="Width"
From="100" To="150" Duration="0:0:1"/>
<DoubleAnimation Storyboard.TargetProperty="Height"
From="50" To="75" Duration="0:0:1"/>
</Storyboard>
</Window.Resources>
<Button Content="Animate Me" Click="Button_Click">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource GrowButtonAnimation}"/>
</EventTrigger>
</Button.Triggers>
</Button>
在这个例子中,我们定义了一个 Storyboard
,其中包含两个 DoubleAnimation
,分别对按钮的 Width
和 Height
属性进行动画处理。From
属性指定起始值,To
属性指定结束值,Duration
属性指定动画的持续时间。当按钮被点击时,触发 EventTrigger
,开始播放动画。
6.1.2 关键帧动画
关键帧动画允许在动画过程中定义多个关键帧,每个关键帧指定一个特定的时间点和属性值。例如,实现一个按钮在不同时间点改变颜色的动画:
<Window.Resources>
<Storyboard x:Key="ColorAnimationStoryboard">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)" Duration="0:0:3">
<LinearColorKeyFrame Value="Red" KeyTime="0:0:0"/>
<LinearColorKeyFrame Value="Green" KeyTime="0:0:1"/>
<LinearColorKeyFrame Value="Blue" KeyTime="0:0:2"/>
<LinearColorKeyFrame Value="Yellow" KeyTime="0:0:3"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Button Content="Color Animate" Click="Button_Click_1">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource ColorAnimationStoryboard}"/>
</EventTrigger>
</Button.Triggers>
</Button>
这里使用 ColorAnimationUsingKeyFrames
定义了一个颜色动画,通过 LinearColorKeyFrame
指定了不同时间点的颜色值。动画会在指定的时间内依次过渡到各个关键帧的颜色。
6.1.3 路径动画
路径动画允许元素沿着指定的路径移动。例如,让一个椭圆沿着一个圆形路径移动:
<Window.Resources>
<Storyboard x:Key="PathAnimationStoryboard">
<PointAnimationUsingPath Storyboard.TargetProperty="(Canvas.LeftProperty)"
Storyboard.TargetName="ellipse"
Duration="0:0:5"
RepeatBehavior="Forever">
<PointAnimationUsingPath.PathGeometry>
<EllipseGeometry Center="200,200" RadiusX="100" RadiusY="100"/>
</PointAnimationUsingPath.PathGeometry>
</PointAnimationUsingPath>
<PointAnimationUsingPath Storyboard.TargetProperty="(Canvas.TopProperty)"
Storyboard.TargetName="ellipse"
Duration="0:0:5"
RepeatBehavior="Forever">
<PointAnimationUsingPath.PathGeometry>
<EllipseGeometry Center="200,200" RadiusX="100" RadiusY="100"/>
</PointAnimationUsingPath.PathGeometry>
</PointAnimationUsingPath>
</Storyboard>
</Window.Resources>
<Canvas>
<Ellipse x:Name="ellipse" Width="20" Height="20" Fill="Red" Canvas.Left="100" Canvas.Top="100"/>
<Button Content="Start Animation" Canvas.Left="10" Canvas.Top="10" Click="Button_Click_2">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource PathAnimationStoryboard}"/>
</EventTrigger>
</Button.Triggers>
</Button>
</Canvas>
在这个示例中,使用 PointAnimationUsingPath
让椭圆沿着一个圆形路径移动,路径由 EllipseGeometry
定义。RepeatBehavior="Forever"
表示动画会无限循环播放。
6.2 多媒体支持
WPF 提供了对多媒体的支持,包括音频和视频的播放。可以使用 MediaElement
控件来播放多媒体文件。
播放视频
<MediaElement Source="video.mp4" Width="640" Height="360"
LoadedBehavior="Play" UnloadedBehavior="Stop"/>
在这个示例中,MediaElement
控件的 Source
属性指定了要播放的视频文件的路径。LoadedBehavior
属性设置为 Play
表示当控件加载完成后自动播放视频,UnloadedBehavior
属性设置为 Stop
表示当控件卸载时停止播放视频。
播放音频
<MediaElement Source="audio.mp3" Width="0" Height="0"
LoadedBehavior="Play" UnloadedBehavior="Stop"/>
对于音频播放,我们可以将控件的宽度和高度设置为 0,使其在界面上不可见。同样,通过 Source
属性指定音频文件的路径,通过 LoadedBehavior
和 UnloadedBehavior
属性控制播放和停止行为。
七、命令系统
7.1 命令基础
WPF 的命令系统提供了一种将用户操作(如按钮点击、菜单项选择等)与业务逻辑分离的机制。命令是一种抽象的操作,它定义了操作的执行逻辑和是否可以执行的判断逻辑。
7.2 实现命令
WPF 中常用的命令实现方式是使用 RelayCommand
类,它是一个自定义的命令类,实现了 ICommand
接口。
RelayCommand 类的实现
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
在 ViewModel 中使用 RelayCommand
public class MainViewModel
{
public RelayCommand MyCommand { get; set; }
public MainViewModel()
{
MyCommand = new RelayCommand(ExecuteMyCommand, CanExecuteMyCommand);
}
private void ExecuteMyCommand(object parameter)
{
// 执行命令的逻辑
MessageBox.Show("Command executed!");
}
private bool CanExecuteMyCommand(object parameter)
{
// 判断命令是否可以执行的逻辑
return true;
}
}
在 XAML 中绑定命令
<Button Content="Execute Command" Command="{Binding MyCommand}"/>
当按钮被点击时,会调用 MainViewModel
中的 ExecuteMyCommand
方法。
7.3 系统命令
WPF 还提供了一些内置的系统命令,如 ApplicationCommands.New
、ApplicationCommands.Open
、ApplicationCommands.Save
等。这些命令可以直接在 XAML 中使用,并且会自动处理一些常见的操作,如快捷键绑定、命令状态更新等。
<Menu>
<MenuItem Header="File">
<MenuItem Header="New" Command="{x:Static ApplicationCommands.New}"/>
<MenuItem Header="Open" Command="{x:Static ApplicationCommands.Open}"/>
<MenuItem Header="Save" Command="{x:Static ApplicationCommands.Save}"/>
</MenuItem>
</Menu>
通过使用系统命令,我们可以快速实现一些常见的功能,提高开发效率。
八、依赖属性和路由事件
8.1 依赖属性
8.1.1 依赖属性基础
依赖属性是 WPF 中的一种特殊属性,它的行为不同于普通的.NET 属性。依赖属性由 DependencyProperty
类表示,它支持数据绑定、样式设置、动画等功能。依赖属性的值可以由多种因素决定,如默认值、样式设置、数据绑定等。
8.1.2 定义依赖属性
要定义一个依赖属性,需要遵循以下步骤:
public class MyControl : Control
{
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(MyControl), new PropertyMetadata(0));
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
}
在这个例子中,我们定义了一个名为 MyProperty
的依赖属性,类型为 int
,所属的类型是 MyControl
,默认值为 0。通过 GetValue
和 SetValue
方法来获取和设置依赖属性的值。
8.1.3 依赖属性的用途
依赖属性的主要用途包括:
- 数据绑定:依赖属性可以作为数据绑定的目标,实现 UI 元素与数据源的双向绑定。
- 样式设置:可以在样式中设置依赖属性的值,实现统一的外观风格。
- 动画效果:可以对依赖属性进行动画处理,创建各种动态效果。
8.2 路由事件
8.2.1 路由事件基础
路由事件是 WPF 中一种特殊的事件机制,它允许事件在元素树中进行传播。路由事件有三种传播方式:冒泡、隧道和直接。
- 冒泡事件:事件从事件源开始,向上级元素依次传播,直到到达根元素或事件被处理。
- 隧道事件:事件从根元素开始,向下级元素依次传播,直到到达事件源或事件被处理。
- 直接事件:事件只在事件源上触发,不会在元素树中传播。