命令是一种设计模式(命令模式,Command Pattern),用于将“请求”封装为一个对象,从而:
解耦调用者(如按钮)与执行者(如 ViewModel 中的方法)
支持统一的启用/禁用控制(CanExecute)
实现撤销(Undo)、日志、队列等高级功能
在 WPF 中,命令通过 ICommand 接口实现。
1public interface ICommand 2{ 3 event EventHandler CanExecuteChanged; 4 bool CanExecute(object parameter); 5 void Execute(object parameter); 6} 7
Execute:执行命令时调用的方法(相当于“点击后做什么”)
CanExecute:决定命令是否可用(返回 true/false,影响按钮是否可点击)
CanExecuteChanged:当命令的可用状态可能改变时触发,通知 UI 刷新(如按钮自动变灰)
🌟 一、先打个比方:命令就像“遥控器”
想象你有一个电视(代表界面上的按钮),还有一个遥控器(代表命令)。
- 你按遥控器上的“开机”键(相当于点击按钮),
- 遥控器会把“开机”这个指令发给电视,
- 电视收到后就执行“打开屏幕”。
在这个过程中:
- 你不需要知道电视内部怎么工作的(解耦),
- 如果电视没插电,遥控器会自动变灰,按不了(自动禁用)。
👉 WPF 的命令就是这个“遥控器”:它把“用户操作”和“实际要做的事”分开,让代码更清晰、更好维护。
🧱 二、命令的核心:ICommand 接口
在 WPF 中,所有命令都要实现一个叫 ICommand 的接口。它只有两个关键方法:
| 方法 | 作用 | 类比 |
|---|---|---|
| Execute() | 真正要做的事情(比如保存文件) | 按下遥控器,电视开机 |
| CanExecute() | 判断现在能不能执行(比如没填内容就不能保存) | 电视没电了,遥控器自动锁住 |
💡 还有一个事件
CanExecuteChanged,用来告诉界面:“我的可用状态变了,请刷新按钮颜色!”
🛠️ 三、自己做一个简单的命令(RelayCommand)
.NET 没有直接提供现成的命令类,所以我们通常自己写一个通用的命令类,叫 RelayCommand。
第一步:创建 RelayCommand
1using System; 2using System.Windows.Input; 3 4public class RelayCommand : ICommand 5{ 6 private readonly Action _execute; // 要执行的方法 7 private readonly Func<bool> _canExecute; // 判断能不能执行 8 9 public RelayCommand(Action execute, Func<bool> canExecute = null) 10 { 11 _execute = execute; 12 _canExecute = canExecute; 13 } 14 15 // 谁想知道我能不能用?我状态变了会通知你! 16 public event EventHandler CanExecuteChanged 17 { 18 add { CommandManager.RequerySuggested += value; } 19 remove { CommandManager.RequerySuggested -= value; } 20 } 21 22 // 能不能执行? 23 public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true; 24 25 // 执行! 26 public void Execute(object parameter) => _execute(); 27} 28
✅ 这个类你可以复制到任何 WPF 项目里复用!
🏗️ 四、在 ViewModel 中使用命令(MVVM 模式)
假设我们做一个“留言本”:输入文字,点“保存”按钮。
1. 创建 ViewModel
1public class MainViewModel 2{ 3 // 用户输入的内容 4 public string Message { get; set; } 5 6 // 保存命令 7 public ICommand SaveCommand { get; } 8 9 // 构造函数 10 public MainViewModel() 11 { 12 SaveCommand = new RelayCommand( 13 execute: OnSave, // 点击时做什么 14 canExecute: CanSave // 什么时候能点 15 ); 16 } 17 18 // 真正的保存逻辑 19 private void OnSave() 20 { 21 MessageBox.Show($"保存成功:{Message}"); 22 } 23 24 // 判断:只有输入了内容才能保存 25 private bool CanSave() 26 { 27 return !string.IsNullOrWhiteSpace(Message); 28 } 29} 30
🖼️ 五、在 XAML 中绑定命令
1<Window x:Class="MyApp.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="留言本" Width="300" Height="150"> 5 6 <StackPanel Margin="10"> 7 <!-- 输入框,绑定到 Message --> 8 <TextBox Text="{Binding Message, UpdateSourceTrigger=PropertyChanged}" 9 Margin="0,0,0,10" /> 10 11 <!-- 按钮,绑定到 SaveCommand --> 12 <Button Content="保存" 13 Command="{Binding SaveCommand}" 14 Height="30" /> 15 </StackPanel> 16</Window> 17
关键点解释:
Text="{Binding Message, UpdateSourceTrigger=PropertyChanged}"
→ 用户一打字,Message属性立刻更新。Command="{Binding SaveCommand}"
→ 按钮自动监听命令的CanExecute:
✅ 完全不用写后台代码(.xaml.cs)!
🔁 六、命令 vs 传统事件(对比)
| 方式 | 传统事件 | 命令(Command) |
|---|---|---|
| 代码位置 | 写在 .xaml.cs(Code-Behind) | 写在 ViewModel(逻辑层) |
| 按钮禁用 | 手动写 button.IsEnabled = false; | 自动根据 CanExecute 控制 |
| 可测试性 | 难(依赖 UI) | 容易(纯 C# 类,可单元测试) |
| 复用性 | 低 | 高(同一个命令可用于按钮、菜单、快捷键) |
🎯 命令让界面和逻辑彻底分离,是 MVVM 的核心!
🎁 七、额外小技巧
1. 给命令传参数
比如删除某条记录,需要传 ID:
1// 命令定义 2public ICommand DeleteCommand { get; } 3 4DeleteCommand = new RelayCommand<string>(id => DeleteItem(id)); 5 6// XAML 7<Button Content="删除" 8 Command="{Binding DeleteCommand}" 9 CommandParameter="123" /> 10
2. 快捷键也能用命令!
1<Window.InputBindings> 2 <KeyBinding Key="S" Modifiers="Ctrl" Command="{Binding SaveCommand}" /> 3</Window.InputBindings> 4
→ 按 Ctrl+S 就能保存,和按钮共用同一个命令!
✅ 总结:一句话记住命令
命令 = 把“按钮点击”变成“可绑定、可控制、可复用”的逻辑对象。
它让你:
- 不用写
.xaml.cs代码 - 按钮自动变灰/变亮
- 逻辑集中、易于测试
- 支持快捷键、菜单、按钮统一处理