1.7. WPF 基础

WPF(Windows Presentation Foundation)是微软新一代的桌面 UI 框架, 采用 XAML + 数据驱动的设计理念。

1.7.1. WPF 概述

特性

WinForms

WPF

渲染引擎

GDI+(像素级)

DirectX(硬件加速)

UI 定义

代码(设计器生成)

XAML(声明式)

布局模型

绝对定位

流式布局(Panel/Grid)

数据绑定

手动

内置 + 双向绑定

样式

逐控件设置

样式/模板/触发器

可定制性

有限

任意控件可完全重模板

学习曲线

平缓

较陡(MVVM + 模板)

1.7.2. XAML 基础

MainWindow.xaml
<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF Demo" Height="450" Width="800">

    <Grid>
        <!-- 定义行和列 -->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 工具栏 -->
        <ToolBar Grid.Row="0">
            <Button Content="打开"/>
            <Button Content="保存"/>
        </ToolBar>

        <!-- 内容区域 -->
        <ListBox Grid.Row="1"
                 ItemsSource="{Binding Users}"
                 SelectedItem="{Binding SelectedUser}"
                 DisplayMemberPath="Name"/>

        <!-- 状态栏 -->
        <StatusBar Grid.Row="2">
            <TextBlock Text="{Binding StatusText}"/>
        </StatusBar>
    </Grid>
</Window>

1.7.3. 布局系统

<!-- Grid:最灵活的布局容器 -->
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>   <!-- 自适应 -->
        <ColumnDefinition Width="*"/>      <!-- 剩余空间 -->
        <ColumnDefinition Width="2*"/>     <!-- 比例 -->
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <TextBlock Grid.Row="0" Grid.Column="0" Text="名称:"/>
    <TextBox  Grid.Row="0" Grid.Column="1" Text="{Binding Name}"/>
</Grid>

<!-- StackPanel:水平/垂直堆叠 -->
<StackPanel Orientation="Horizontal">
    <Button Content="确定" Margin="5"/>
    <Button Content="取消" Margin="5"/>
</StackPanel>

<!-- WrapPanel:自动换行 -->
<WrapPanel>
    <Button Content="标签1"/>
    <Button Content="标签2"/>
    <!-- 空间不足自动换行 -->
</WrapPanel>

<!-- DockPanel:停靠布局 -->
<DockPanel>
    <Menu DockPanel.Dock="Top">...</Menu>
    <StatusBar DockPanel.Dock="Bottom">...</StatusBar>
    <TextBox/>   <!-- 填充剩余空间 -->
</DockPanel>

1.7.4. 数据绑定

1.7.4.1. 核心概念

绑定模式:
OneWay      :源 → 目标(默认)
TwoWay      :源 ↔ 目标
OneTime     :初始化时一次
OneWayToSource:目标 → 源

绑定方向:Source (数据源)→ Target (UI 控件)

1.7.4.2. XAML 绑定语法

<!-- 基本绑定 -->
<TextBox Text="{Binding UserName}"/>
<TextBlock Text="{Binding FullName, FallbackValue='N/A'}"/>

<!-- 格式化 -->
<TextBlock Text="{Binding Price, StringFormat={}¥{0:N2}}"/>
<TextBlock Text="{Binding Date, StringFormat={}{0:yyyy-MM-dd}}"/>

<!-- 转换器 -->
<TextBlock Text="{Binding Status, Converter={StaticResource StatusConverter}}"/>

<!-- 相对源 -->
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window},
                          Path=Title}"/>

<!-- 通知属性变更 -->
<CheckBox IsChecked="{Binding IsActive, Mode=TwoWay}"/>

1.7.4.3. INotifyPropertyChanged

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void SetProperty<T>(ref T field, T value,
        [CallerMemberName] string? propertyName = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            PropertyChanged?.Invoke(this,
                new PropertyChangedEventArgs(propertyName));
        }
    }
}

public class MainViewModel : ViewModelBase
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

1.7.5. 命令(ICommand)

// RelayCommand 实现
public class RelayCommand : ICommand
{
    private readonly Action<object?> _execute;
    private readonly Func<object?, bool>? _canExecute;

    public RelayCommand(Action<object?> execute,
                       Func<object?, bool>? canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object? parameter)
        => _canExecute?.Invoke(parameter) ?? true;

    public void Execute(object? parameter)
        => _execute(parameter);

    public event EventHandler? CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}
<Button Content="保存"
        Command="{Binding SaveCommand}"
        CommandParameter="{Binding Id}"/>
// ViewModel 中的命令
public ICommand SaveCommand { get; }

public MainViewModel()
{
    SaveCommand = new RelayCommand(
        execute: param => SaveData(param),
        canExecute: _ => !IsBusy);
}

1.7.6. 样式与资源

<!-- 资源字典 -->
<Window.Resources>
    <!-- 样式 -->
    <Style TargetType="Button" x:Key="PrimaryButton">
        <Setter Property="Background" Value="#0078D4"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Padding" Value="12,6"/>
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Background="{TemplateBinding Background}"
                            CornerRadius="4">
                        <ContentPresenter HorizontalAlignment="Center"
                                          VerticalAlignment="Center"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- 隐式样式(自动应用到所有 Button) -->
    <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}"/>

    <!-- 数据模板 -->
    <DataTemplate x:Key="UserTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" FontWeight="Bold"/>
            <TextBlock Text="{Binding Email}" Margin="10,0,0,0"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>