ICommand
接口的实现原理,并提供企业级应用中的最佳实践方案。
通过自定义命令类解耦UI与业务逻辑:
基础实现模板:
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 bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
// 支持泛型参数的增强版
public class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Func<T, bool> _canExecute;
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) =>
_canExecute?.Invoke((T)parameter) ?? true;
public void Execute(object parameter) => _execute((T)parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
ViewModel中的使用示例:
public class MainViewModel
{
public RelayCommand SaveCommand { get; }
public RelayCommand<string> SearchCommand { get; }
public MainViewModel()
{
SaveCommand = new RelayCommand(ExecuteSave, CanSave);
SearchCommand = new RelayCommand<string>(ExecuteSearch);
}
private void ExecuteSave() => /* 保存逻辑 */;
private bool CanSave() => !string.IsNullOrEmpty(Content);
private void ExecuteSearch(string keyword) => /* 搜索逻辑 */;
}
命令的可用性状态与UI元素自动同步:
XAML绑定示例:
<Button Content="保存"
Command="{Binding SaveCommand}"
IsEnabled="{Binding SaveCommand.IsEnabled}"/>
动态更新策略:
// 通过CommandManager自动触发
CommandManager.InvalidateRequerySuggested();
// 在属性变更时触发
public string Content
{
set
{
_content = value;
OnPropertyChanged();
SaveCommand.RaiseCanExecuteChanged();
}
}
禁用状态样式优化:
<Style TargetType="Button">
"IsEnabled" Value="False">
"Opacity" Value="0.5"/>
Style>
支持多种参数传递方式:
<Button Command="{Binding StatusCommand}"
CommandParameter="Approved"/>
<ComboBox x:Name="statusList" SelectedValuePath="Tag"/>
<Button Command="{Binding UpdateCommand}"
CommandParameter="{Binding SelectedItem.Tag, ElementName=statusList}"/>
// ViewModel
public RelayCommand<User> EditCommand { get; } =
new RelayCommand<User>(user => /* 编辑逻辑 */);
// XAML
<ListBox x:Name="userList">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="编辑"
Command="{Binding DataContext.EditCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"/>
DataTemplate>
ListBox.ItemTemplate>
ListBox>
处理长时间运行任务的最佳实践:
异步命令模板:
public class AsyncCommand : ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private bool _isExecuting;
public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) =>
!_isExecuting && (_canExecute?.Invoke() ?? true);
public async void Execute(object parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
RaiseCanExecuteChanged();
await _execute();
}
finally
{
_isExecuting = false;
RaiseCanExecuteChanged();
}
}
}
public void RaiseCanExecuteChanged() =>
CommandManager.InvalidateRequerySuggested();
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
使用示例:
public AsyncCommand LoadDataCommand { get; }
public MainViewModel()
{
LoadDataCommand = new AsyncCommand(LoadDataAsync, () => !IsLoading);
}
private async Task LoadDataAsync()
{
IsLoading = true;
try
{
await DataService.FetchData();
}
finally
{
IsLoading = false;
}
}
问题1:命令不触发
CanExecute
返回值是否为true
DataContext
是否正确继承RelayCommand
时)问题2:CanExecute不自动更新
CommandManager.InvalidateRequerySuggested()
Dispatcher
调用:Application.Current.Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
问题3:参数绑定失败
<Button CommandParameter="{Binding SelectedItem, Converter={local:DebugConverter}}"/>
问题4:内存泄漏
public void Dispose()
{
SaveCommand.CanExecuteChanged -= OnSaveCommandChanged;
}
本章小结
通过本章学习,开发者应掌握:
RelayCommand
CanExecute
控制UI状态建议实践以下场景:
下一章将深入讲解MVVM模式的核心架构与实现细节。