WPF中ICommand使用指南
在 WPF 中,ICommand 是实现 命令模式 的核心接口,用于解耦 UI 操作(如按钮点击)和业务逻辑。以下是关键知识点的总结:
1. ICommand 接口定义
public interface ICommand
{
    event EventHandler CanExecuteChanged;
    bool CanExecute(object parameter);
    void Execute(object parameter);
}
- 
CanExecute:判断命令是否可执行(返回
false时,绑定控件自动禁用) - 
Execute:执行命令的实际逻辑
 - 
CanExecuteChanged:通知命令状态变化的事件
 
2. 常用实现方式
(1) 自定义 RelayCommand(最常用)
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 ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }
    public bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) ?? true;
    public void Execute(object parameter) => _execute(parameter);
    // 通过 CommandManager 自动刷新状态
    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}
(2) 使用 CommunityToolkit.Mvvm 的 RelayCommand
// 安装 NuGet 包:CommunityToolkit.Mvvm
using CommunityToolkit.Mvvm.Input;
public class MyViewModel
{
    public ICommand ClickCommand { get; }
    public MyViewModel()
    {
        ClickCommand = new RelayCommand(ExecuteClick, CanClick);
    }
    private void ExecuteClick() => Console.WriteLine("Clicked!");
    private bool CanClick() => true;
}
3. 在 ViewModel 中声明命令
public class MainViewModel : INotifyPropertyChanged
{
    private string _inputText;
    public string InputText
    {
        get => _inputText;
        set => SetField(ref _inputText, value);
    }
    public ICommand SubmitCommand { get; }
    public MainViewModel()
    {
        SubmitCommand = new RelayCommand(
            execute: _ => Submit(),
            canExecute: _ => !string.IsNullOrWhiteSpace(InputText)
        );
    }
    private void Submit() => MessageBox.Show($"Submitted: {InputText}");
}
4. XAML 中绑定命令
<Button Content="提交" 
        Command="{Binding SubmitCommand}"
        CommandParameter="可选参数"/>
        
<!-- 绑定带参数的命令 -->
<Button Command="{Binding OpenFileCommand}"
        CommandParameter="{Binding SelectedItem}"/>
5. 自动刷新命令状态
- 
方法1:依赖
CommandManager(推荐)
如上文RelayCommand实现,通过CommandManager.RequerySuggested自动触发状态刷新(例如文本框输入时)。 - 
方法2:手动触发
调用CommandManager.InvalidateRequerySuggested()强制刷新所有命令:public string InputText { get => _inputText; set { _inputText = value; OnPropertyChanged(); CommandManager.InvalidateRequerySuggested(); // 手动刷新 } } 
6. 处理异步操作
使用 async/await 的异步命令示例:
public class AsyncRelayCommand : ICommand
{
    private readonly Func<object, Task> _execute;
    private bool _isExecuting;
    public AsyncRelayCommand(Func<object, Task> execute) => _execute = execute;
    public bool CanExecute(object parameter) => !_isExecuting;
    public async void Execute(object parameter)
    {
        if (_isExecuting) return;
        
        _isExecuting = true;
        OnCanExecuteChanged();
        
        try { await _execute(parameter); }
        finally
        {
            _isExecuting = false;
            OnCanExecuteChanged();
        }
    }
    private void OnCanExecuteChanged() => 
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
7. 内置命令类型
- 
RoutedCommand:支持路由事件(冒泡/隧道),常用于菜单快捷键
 - 
RoutedUICommand:带文本描述的
RoutedCommand 
// 示例:绑定 ApplicationCommands 内置命令
<Button Command="ApplicationCommands.Copy"/>
8. 最佳实践
- 
ViewModel 中实现命令:保持 UI 与逻辑分离
 - 
避免命令阻塞 UI:耗时操作使用异步命令
 - 
参数传递:通过
CommandParameter传递上下文数据 - 
禁用状态可视化:依赖
CanExecute自动禁用按钮 
完整示例
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
    public ICommand ClearTextCommand { get; }
    private string _text;
    
    public string Text
    {
        get => _text;
        set => SetField(ref _text, value);
    }
    public MainViewModel()
    {
        ClearTextCommand = new RelayCommand(
            _ => Text = "",
            _ => !string.IsNullOrEmpty(Text)
        );
    }
    
    // INotifyPropertyChanged 实现略...
}
XAML:
<StackPanel>
    <TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
    <Button Content="清除" 
            Command="{Binding ClearTextCommand}"
            Margin="5"/>
</StackPanel>
通过以上实现,当文本框为空时,按钮自动禁用,点击按钮清空文本框内容。
                    
                
                
            
        
浙公网安备 33010602011771号