C# WPF 基础知识学习(四)

五、样式和模板

5.1 样式基础

样式是一种用于集中设置 UI 元素属性的机制,可以将一组属性应用到多个元素上,实现统一的外观风格。样式可以定义在资源字典中,也可以直接在 XAML 文件中定义。

5.1 样式基础

XML 复制代码
<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 属性来实现。例如:

XML 复制代码
<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 的元素上。例如:

XML 复制代码
<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 用于自定义控件的外观。它可以完全改变控件的可视化结构,而不仅仅是设置属性。例如,自定义一个按钮的模板:

XML 复制代码
<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 元素作为按钮的外层容器,通过 TemplateBindingBorder 的属性绑定到按钮的属性上。ContentPresenter 用于显示按钮的内容。ControlTemplate.Triggers 部分定义了触发条件,当鼠标悬停或按钮被按下时,改变 Border 的背景色。

5.3.2 DataTemplate

DataTemplate 用于定义数据项的呈现方式。当我们将一个集合绑定到一个列表控件(如 ListViewComboBox 等)时,DataTemplate 可以决定每个数据项在界面上的显示形式。例如:

XML 复制代码
<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 对象有 NameAge 属性。PersonDataTemplate 定义了每个 Person 对象在 ListBox 中的显示方式,即使用一个水平的 StackPanel 显示姓名和年龄。

5.4 资源字典

资源字典是一种用于集中管理样式、模板、画笔等资源的机制。可以将资源字典定义在单独的 .xaml 文件中,然后在多个 XAML 文件中引用。

创建资源字典文件

创建一个名为 MyResources.xaml 的文件,内容如下:

XML 复制代码
<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 文件中引用资源字典
XML 复制代码
<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 线性动画

线性动画是最简单的动画类型,它在指定的时间内从一个值线性变化到另一个值。例如,实现一个按钮在点击时逐渐变大的动画:

XML 复制代码
<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,分别对按钮的 WidthHeight 属性进行动画处理。From 属性指定起始值,To 属性指定结束值,Duration 属性指定动画的持续时间。当按钮被点击时,触发 EventTrigger,开始播放动画。

6.1.2 关键帧动画

关键帧动画允许在动画过程中定义多个关键帧,每个关键帧指定一个特定的时间点和属性值。例如,实现一个按钮在不同时间点改变颜色的动画:

XML 复制代码
<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 路径动画

路径动画允许元素沿着指定的路径移动。例如,让一个椭圆沿着一个圆形路径移动:

XML 复制代码
<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 控件来播放多媒体文件。

播放视频
XML 复制代码
<MediaElement Source="video.mp4" Width="640" Height="360"
              LoadedBehavior="Play" UnloadedBehavior="Stop"/>

在这个示例中,MediaElement 控件的 Source 属性指定了要播放的视频文件的路径。LoadedBehavior 属性设置为 Play 表示当控件加载完成后自动播放视频,UnloadedBehavior 属性设置为 Stop 表示当控件卸载时停止播放视频。

播放音频
XML 复制代码
<MediaElement Source="audio.mp3" Width="0" Height="0"
              LoadedBehavior="Play" UnloadedBehavior="Stop"/>

对于音频播放,我们可以将控件的宽度和高度设置为 0,使其在界面上不可见。同样,通过 Source 属性指定音频文件的路径,通过 LoadedBehaviorUnloadedBehavior 属性控制播放和停止行为。

七、命令系统

7.1 命令基础

WPF 的命令系统提供了一种将用户操作(如按钮点击、菜单项选择等)与业务逻辑分离的机制。命令是一种抽象的操作,它定义了操作的执行逻辑和是否可以执行的判断逻辑。

7.2 实现命令

WPF 中常用的命令实现方式是使用 RelayCommand 类,它是一个自定义的命令类,实现了 ICommand 接口。

RelayCommand 类的实现
cs 复制代码
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
cs 复制代码
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 中绑定命令
XML 复制代码
<Button Content="Execute Command" Command="{Binding MyCommand}"/>

当按钮被点击时,会调用 MainViewModel 中的 ExecuteMyCommand 方法。

7.3 系统命令

WPF 还提供了一些内置的系统命令,如 ApplicationCommands.NewApplicationCommands.OpenApplicationCommands.Save 等。这些命令可以直接在 XAML 中使用,并且会自动处理一些常见的操作,如快捷键绑定、命令状态更新等。

XML 复制代码
<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 定义依赖属性

要定义一个依赖属性,需要遵循以下步骤:

cs 复制代码
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。通过 GetValueSetValue 方法来获取和设置依赖属性的值。

8.1.3 依赖属性的用途

依赖属性的主要用途包括:

  • 数据绑定:依赖属性可以作为数据绑定的目标,实现 UI 元素与数据源的双向绑定。
  • 样式设置:可以在样式中设置依赖属性的值,实现统一的外观风格。
  • 动画效果:可以对依赖属性进行动画处理,创建各种动态效果。

8.2 路由事件

8.2.1 路由事件基础

路由事件是 WPF 中一种特殊的事件机制,它允许事件在元素树中进行传播。路由事件有三种传播方式:冒泡、隧道和直接。

  • 冒泡事件:事件从事件源开始,向上级元素依次传播,直到到达根元素或事件被处理。
  • 隧道事件:事件从根元素开始,向下级元素依次传播,直到到达事件源或事件被处理。
  • 直接事件:事件只在事件源上触发,不会在元素树中传播。
相关推荐
百分百题库APP9 分钟前
注册安全工程师考试科目有哪些?
学习·考试·题库·考证
yngsqq40 分钟前
贪心算法和遗传算法优劣对比——c#
算法·贪心算法·c#
鲤籽鲲1 小时前
C# 事件使用详解
开发语言·c#·c# 知识捡漏
余多多_zZ1 小时前
鸿蒙初学者学习手册(HarmonyOSNext_API14)_UIContext(@ohos.arkui.UIContext (UIContext))
笔记·学习·华为·harmonyos
hopetomorrow3 小时前
学习路之TP6 --重写vendor目录下的文件(新建命令)
学习
熊思宇3 小时前
MoonSharp 文档五
c#·moonsharp
奕天者3 小时前
C++学习笔记(十六)——函数重载
c++·笔记·学习
虾球xz4 小时前
游戏引擎学习第155天
学习·游戏引擎
拾2144 小时前
代码比较功能
学习