第 14 章 WPF 基础

14.1 XAML 语法与布局系统

  • XAML 核心语法:XML 格式,描述 UI 结构,支持数据绑定、样式、资源。
  • 常用布局容器

容器

特点

适用场景

Grid

网格布局(行 / 列),支持比例 / 固定尺寸

复杂 UI 布局(如表单、仪表盘)

StackPanel

垂直 / 水平堆叠,控件自动排列

简单列表(如按钮组、菜单)

Canvas

绝对定位(Left/Top),自由布局

自定义图形、图表

DockPanel

停靠布局(Top/Bottom/Left/Right/Center)

窗口框架(如工具栏、状态栏)

  • 实战案例:Grid 布局表单
<!-- MainWindow.xaml -->
<Window x:Class="WpfTodoApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="待办事项应用" Height="450" Width="600">
    <Grid Margin="20">
        <!-- 定义Grid行和列 -->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/> <!-- 标题行(自动高度) -->
            <RowDefinition Height="Auto"/> <!-- 输入行 -->
            <RowDefinition Height="*"/>    <!-- 列表行(占满剩余高度) -->
            <RowDefinition Height="Auto"/> <!-- 状态栏行 -->
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/> <!-- 标签列 -->
            <ColumnDefinition Width="*"/>    <!-- 输入控件列 -->
        </Grid.ColumnDefinitions>

        <!-- 1. 标题 -->
        <TextBlock Grid.Row="0" Grid.ColumnSpan="2" 
                   Text="待办事项管理" 
                   FontSize="24" 
                   FontWeight="Bold" 
                   Margin="0 0 0 10"/>

        <!-- 2. 输入行:标签 -->
        <TextBlock Grid.Row="1" Grid.Column="0" 
                   Text="新增待办:" 
                   VerticalAlignment="Center" 
                   Margin="0 0 10 0"/>
        <!-- 输入行:文本框 -->
        <TextBox Grid.Row="1" Grid.Column="1" 
                 x:Name="TodoInput" 
                 Height="30" 
                 Margin="0 0 10 0"/>
        <!-- 输入行:添加按钮 -->
        <Button Grid.Row="1" Grid.Column="2" 
                Content="添加" 
                Height="30" 
                Width="80" 
                Click="AddTodoButton_Click"/>

        <!-- 3. 待办列表 -->
        <ListBox Grid.Row="2" Grid.ColumnSpan="3" 
                 x:Name="TodoListBox" 
                 Margin="0 10 0 0">
            <!-- 列表项模板 -->
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Height="30" VerticalAlignment="Center">
                        <CheckBox IsChecked="{Binding IsCompleted}" 
                                  Margin="0 0 10 0"/>
                        <TextBlock Text="{Binding Content}" 
                                   FontSize="14" 
                                   Width="300"
                                   TextDecorations="{Binding IsCompleted, Converter={StaticResource BoolToStrikethroughConverter}}"/>
                        <Button Content="删除" 
                                Height="25" 
                                Width="60" 
                                Margin="10 0 0 0"
                                Click="DeleteTodoButton_Click"
                                Tag="{Binding}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <!-- 4. 状态栏 -->
        <TextBlock Grid.Row="3" Grid.ColumnSpan="3" 
                   x:Name="StatusText" 
                   Text="已完成:0 / 总计:0" 
                   Margin="0 10 0 0"
                   HorizontalAlignment="Right"/>
    </Grid>
</Window>

14.2 控件库与样式模板

  • 样式(Style):统一控件外观,可定义在 Window.Resources 或 App.xaml 中。
  • 实战案例:自定义按钮样式
<!-- App.xaml 全局样式 -->
<Application x:Class="WpfTodoApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <!-- 1. 布尔值转删除线转换器(用于待办事项完成状态) -->
        <local:BoolToStrikethroughConverter x:Key="BoolToStrikethroughConverter"/>

        <!-- 2. 自定义按钮样式 -->
        <Style TargetType="Button" x:Key="PrimaryButtonStyle">
            <Setter Property="Background" Value="#2196F3"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="FontSize" Value="14"/>
            <Setter Property="Padding" Value="10 5"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="CornerRadius" Value="4"/>
            <!-- 鼠标悬停效果 -->
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#1976D2"/>
                    <Setter Property="Cursor" Value="Hand"/>
                </Trigger>
                <!-- 按下效果 -->
                <Trigger Property="IsPressed" Value="True">
                    <Setter Property="Background" Value="#1565C0"/>
                </Trigger>
                <!-- 禁用效果 -->
                <Trigger Property="IsEnabled" Value="False">
                    <Setter Property="Background" Value="#BDBDBD"/>
                    <Setter Property="Foreground" Value="#757575"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Application.Resources>
</Application>

<!-- 转换器实现(BoolToStrikethroughConverter.cs) -->
public class BoolToStrikethroughConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool isCompleted && isCompleted)
        {
            return TextDecorations.Strikethrough; // 完成时显示删除线
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

<!-- 使用样式(MainWindow.xaml中按钮) -->
<Button Grid.Row="1" Grid.Column="2" 
        Content="添加" 
        Style="{StaticResource PrimaryButtonStyle}"
        Click="AddTodoButton_Click"/>

14.3 数据绑定与 MVVM 模式

MVVM 核心:Model(数据模型)、View(UI)、ViewModel(业务逻辑,连接 View 和 Model),通过数据绑定实现 UI 与逻辑解耦。

实战案例:待办事项 MVVM 实现

    1. Model(TodoItem.cs)
// 实现INotifyPropertyChanged,属性变更时通知UI
public class TodoItem : INotifyPropertyChanged
{
    private string _content;
    private bool _isCompleted;

    public string Content
    {
        get => _content;
        set
        {
            _content = value;
            OnPropertyChanged();
        }
    }

    public bool IsCompleted
    {
        get => _isCompleted;
        set
        {
            _isCompleted = value;
            OnPropertyChanged();
            // 通知Status变化(触发UI更新已完成数量)
            OnPropertyChanged(nameof(Status));
        }
    }

    // 状态文本(用于UI绑定)
    public string Status => IsCompleted ? "已完成" : "未完成";

    // 实现INotifyPropertyChanged接口
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
    1. ViewModel(TodoViewModel.cs)
public class TodoViewModel : INotifyPropertyChanged
{
    private ObservableCollection<TodoItem> _todoItems;
    private string _newTodoContent;

    public TodoViewModel()
    {
        // 初始化待办列表(ObservableCollection:集合变更时通知UI)
        _todoItems = new ObservableCollection<TodoItem>
        {
            new TodoItem { Content = "学习WPF MVVM", IsCompleted = false },
            new TodoItem { Content = "编写Docker部署文档", IsCompleted = true }
        };

        // 命令:添加待办(ICommand接口,绑定到按钮Click)
        AddTodoCommand = new RelayCommand(AddTodo, CanAddTodo);
        // 命令:删除待办
        DeleteTodoCommand = new RelayCommand<TodoItem>(DeleteTodo);
    }

    // 待办列表(绑定到ListBox)
    public ObservableCollection<TodoItem> TodoItems
    {
        get => _todoItems;
        set
        {
            _todoItems = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(CompletedCount));
            OnPropertyChanged(nameof(TotalCount));
            OnPropertyChanged(nameof(StatusText));
        }
    }

    // 新增待办内容(绑定到TextBox)
    public string NewTodoContent
    {
        get => _newTodoContent;
        set
        {
            _newTodoContent = value;
            OnPropertyChanged();
            // 通知命令可执行状态变更
            (AddTodoCommand as RelayCommand)?.RaiseCanExecuteChanged();
        }
    }

    // 统计信息(绑定到状态栏)
    public int CompletedCount => TodoItems.Count(t => t.IsCompleted);
    public int TotalCount => TodoItems.Count;
    public string StatusText => $"已完成:{CompletedCount} / 总计:{TotalCount}";

    // 命令:添加待办
    public ICommand AddTodoCommand { get; }
    // 命令:删除待办
    public ICommand DeleteTodoCommand { get; }

    // 添加待办逻辑
    private void AddTodo()
    {
        if (!string.IsNullOrWhiteSpace(NewTodoContent))
        {
            TodoItems.Add(new TodoItem { Content = NewTodoContent, IsCompleted = false });
            NewTodoContent = ""; // 清空输入框
        }
    }

    // 判断是否可添加(输入不为空)
    private bool CanAddTodo()
    {
        return !string.IsNullOrWhiteSpace(NewTodoContent);
    }

    // 删除待办逻辑
    private void DeleteTodo(TodoItem todoItem)
    {
        if (TodoItems.Contains(todoItem))
        {
            TodoItems.Remove(todoItem);
            // 通知统计信息变更
            OnPropertyChanged(nameof(CompletedCount));
            OnPropertyChanged(nameof(TotalCount));
            OnPropertyChanged(nameof(StatusText));
        }
    }

    // 监听TodoItems集合中元素的属性变更(更新统计信息)
    public TodoViewModel()
    {
        // ... 原有初始化代码 ...
        // 为每个待办项添加PropertyChanged监听
        foreach (var item in TodoItems)
        {
            item.PropertyChanged += TodoItem_PropertyChanged;
        }
        // 监听集合变更(新增/删除元素时添加/移除监听)
        TodoItems.CollectionChanged += TodoItems_CollectionChanged;
    }

    private void TodoItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (TodoItem item in e.NewItems)
            {
                item.PropertyChanged += TodoItem_PropertyChanged;
            }
        }
        if (e.OldItems != null)
        {
            foreach (TodoItem item in e.OldItems)
            {
                item.PropertyChanged -= TodoItem_PropertyChanged;
            }
        }
    }

    private void TodoItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(TodoItem.IsCompleted))
        {
            OnPropertyChanged(nameof(CompletedCount));
            OnPropertyChanged(nameof(StatusText));
        }
    }

    // INotifyPropertyChanged实现
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// 辅助类:RelayCommand(实现ICommand,简化命令创建)
public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

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

    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

    public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
    public void Execute(object parameter) => _execute();
}

// 泛型RelayCommand(支持参数)
public class RelayCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    private readonly Predicate<T> _canExecute;

    public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

    public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;
    public void Execute(object parameter) => _execute((T)parameter);
}
    1. View 绑定 ViewModel(MainWindow.xaml)
<!-- 设置DataContext为ViewModel -->
<Window x:Class="WpfTodoApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfTodoApp"
        Title="待办事项应用(MVVM)" Height="450" Width="600">
    <!-- 绑定ViewModel(可通过依赖注入实现,此处简化为直接创建) -->
    <Window.DataContext>
        <local:TodoViewModel/>
    </Window.DataContext>
    <Grid Margin="20">
        <!-- 省略Grid行列定义,同前 -->
        
        <!-- 输入框:绑定NewTodoContent -->
        <TextBox Grid.Row="1" Grid.Column="1" 
                 Text="{Binding NewTodoContent, UpdateSourceTrigger=PropertyChanged}" 
                 Height="30" 
                 Margin="0 0 10 0"
                 PlaceholderText="输入待办事项..."/>
        <!-- 添加按钮:绑定AddTodoCommand -->
        <Button Grid.Row="1" Grid.Column="2" 
                Content="添加" 
                Style="{StaticResource PrimaryButtonStyle}"
                Command="{Binding AddTodoCommand}"/>

        <!-- 待办列表:绑定TodoItems -->
        <ListBox Grid.Row="2" Grid.ColumnSpan="3" 
                 ItemsSource="{Binding TodoItems}" 
                 Margin="0 10 0 0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Height="30" VerticalAlignment="Center">
                        <CheckBox IsChecked="{Binding IsCompleted, UpdateSourceTrigger=PropertyChanged}" 
                                  Margin="0 0 10 0"/>
                        <TextBlock Text="{Binding Content}" 
                                  FontSize="14" 
                                  Width="300"
                                  TextDecorations="{Binding IsCompleted, Converter={StaticResource BoolToStrikethroughConverter}}"/>
                        <!-- 删除按钮:绑定DeleteTodoCommand,传递当前待办项作为参数 -->
                        <Button Content="删除" 
                                Style="{StaticResource PrimaryButtonStyle}"
                                Height="25" 
                                Width="60" 
                                Margin="10 0 0 0"
                                Command="{Binding DataContext.DeleteTodoCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
                                CommandParameter="{Binding}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <!-- 状态栏:绑定StatusText -->
        <TextBlock Grid.Row="3" Grid.ColumnSpan="3" 
                  Text="{Binding StatusText}" 
                  Margin="0 10 0 0"
                  HorizontalAlignment="Right"/>
    </Grid>
</Window>

14.4 命令系统与用户交互

命令系统:通过ICommand接口实现 UI 交互(如按钮点击)与业务逻辑解耦,无需在 Code-Behind 写 Click 事件。

实战案例:已在 MVVM 案例中实现AddTodoCommand和DeleteTodoCommand,命令自动根据CanExecute控制按钮是否可用(如输入为空时 “添加” 按钮禁用)。

14.5 资源字典与主题定制

  • 资源字典:分离资源(样式、模板、转换器),便于复用和主题切换。
  • 实战案例:主题切换
    1. 创建主题资源字典
      • Themes/LightTheme.xaml(浅色主题):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="WindowBackground" Color="#F5F5F5"/>
    <SolidColorBrush x:Key="TextColor" Color="#333333"/>
    <SolidColorBrush x:Key="PrimaryColor" Color="#2196F3"/>
</ResourceDictionary>
      • Themes/DarkTheme.xaml(深色主题):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="WindowBackground" Color="#212121"/>
    <SolidColorBrush x:Key="TextColor" Color="#FFFFFF"/>
    <SolidColorBrush x:Key="PrimaryColor" Color="#42A5F5"/>
</ResourceDictionary>
    1. 在 App.xaml 中合并资源字典
<Application.Resources>
  <!-- 合并基础资源 -->
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <!-- 默认浅色主题 -->
      <ResourceDictionary x:Key="LightTheme" Source="Themes/LightTheme.xaml"/>
      <ResourceDictionary x:Key="DarkTheme" Source="Themes/DarkTheme.xaml"/>
      <!-- 当前主题(绑定到ViewModel的Theme属性) -->
      <ResourceDictionary Source="{Binding Theme, Converter={StaticResource ThemeToSourceConverter}}"/>
    </ResourceDictionary.MergedDictionaries>

    <!-- 其他资源(转换器、样式) -->
    <local:BoolToStrikethroughConverter x:Key="BoolToStrikethroughConverter"/>
    <local:ThemeTo</doubaocanvas>
© 版权声明

相关文章

暂无评论

none
暂无评论...