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 实现:
- 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));
}
}
- 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);
}
- 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 资源字典与主题定制
- 资源字典:分离资源(样式、模板、转换器),便于复用和主题切换。
- 实战案例:主题切换:
- 创建主题资源字典:
- 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>
- 在 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>
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...