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. 最佳实践

  1. ViewModel 中实现命令:保持 UI 与逻辑分离

  2. 避免命令阻塞 UI:耗时操作使用异步命令

  3. 参数传递:通过 CommandParameter 传递上下文数据

  4. 禁用状态可视化:依赖 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>

通过以上实现,当文本框为空时,按钮自动禁用,点击按钮清空文本框内容。

posted @ 2025-06-15 11:39  若水如引  阅读(257)  评论(0)    收藏  举报