Prism 命令详解:从遥控器到智能家居的编程哲学
一、命令是什么?—— 生活中的遥控器类比
想象一下,你有一个电视遥控器:
-
遥控器按钮 = UI元素(按钮、菜单等)
-
按按钮的动作 = 用户交互事件
-
电视的反应 = 后台业务逻辑
在Prism中,命令(Command)就是连接UI动作与后台逻辑的"遥控器机制"。
csharp
// 传统事件处理(直接接线)—— 紧耦合
button.Click += (s, e) => { /* 直接在这里写业务逻辑 */ };
// Prism命令(通过遥控器)—— 松耦合
// View: 按钮按下 -> Command
// ViewModel: 接收命令 -> 执行业务逻辑
二、DelegateCommand:最基本的命令类型
2.1 基础使用:简单的开灯场景
csharp
// ViewModel中
using Prism.Commands;
using Prism.Mvvm;
public class LightViewModel : BindableBase
{
private bool _isLightOn;
public bool IsLightOn
{
get => _isLightOn;
set => SetProperty(ref _isLightOn, value);
}
// 1. 定义命令属性
private DelegateCommand _toggleLightCommand;
public DelegateCommand ToggleLightCommand =>
_toggleLightCommand ?? (_toggleLightCommand = new DelegateCommand(ExecuteToggleLight));
// 2. 执行方法
private void ExecuteToggleLight()
{
IsLightOn = !IsLightOn;
Console.WriteLine($"灯现在是 {(IsLightOn ? "开" : "关")}");
}
}
xml
<!-- View中 -->
<Button Content="开关灯"
Command="{Binding ToggleLightCommand}" />
2.2 带参数的命令:调节音量
csharp
public class AudioViewModel : BindableBase
{
private int _volume = 50;
public int Volume
{
get => _volume;
set => SetProperty(ref _volume, value);
}
// 带参数的命令(使用泛型DelegateCommand<T>)
private DelegateCommand<int> _adjustVolumeCommand;
public DelegateCommand<int> AdjustVolumeCommand =>
_adjustVolumeCommand ?? (_adjustVolumeCommand = new DelegateCommand<int>(ExecuteAdjustVolume));
private void ExecuteAdjustVolume(int change)
{
Volume = Math.Max(0, Math.Min(100, Volume + change));
Console.WriteLine($"音量调整到: {Volume}");
}
}
xml
<Button Content="音量+" Command="{Binding AdjustVolumeCommand}"
CommandParameter="10" />
<Button Content="音量-" Command="{Binding AdjustVolumeCommand}"
CommandParameter="-10" />
2.3 带条件判断的命令:需要钥匙才能启动汽车
csharp
public class CarViewModel : BindableBase
{
private bool _hasKey = false;
private bool _isEngineRunning = false;
public bool HasKey
{
get => _hasKey;
set
{
SetProperty(ref _hasKey, value);
// 钥匙状态改变时,重新评估命令可执行性
StartEngineCommand.RaiseCanExecuteChanged();
}
}
// 带CanExecute检查的命令
private DelegateCommand _startEngineCommand;
public DelegateCommand StartEngineCommand =>
_startEngineCommand ?? (_startEngineCommand = new DelegateCommand(
ExecuteStartEngine, // 执行逻辑
CanStartEngine // 条件判断逻辑
));
private void ExecuteStartEngine()
{
_isEngineRunning = true;
Console.WriteLine("引擎启动了!");
}
private bool CanStartEngine()
{
// 只有有钥匙时才能启动
return HasKey;
}
}
三、CompositeCommand:组合命令 - 智能家居场景
想象一个"离家模式"按钮,需要同时:
-
关闭所有灯
-
关闭空调
-
启动安防系统
csharp
// 1. 定义各个独立的命令
public class LightSystemViewModel : BindableBase
{
public DelegateCommand TurnOffAllLightsCommand { get; }
public LightSystemViewModel()
{
TurnOffAllLightsCommand = new DelegateCommand(() =>
{
Console.WriteLine("所有灯已关闭");
});
}
}
public class ACSystemViewModel : BindableBase
{
public DelegateCommand TurnOffACCommand { get; }
public ACSystemViewModel()
{
TurnOffACCommand = new DelegateCommand(() =>
{
Console.WriteLine("空调已关闭");
});
}
}
// 2. 主ViewModel定义组合命令
public class SmartHomeViewModel : BindableBase
{
public CompositeCommand LeaveHomeCommand { get; }
private LightSystemViewModel _lightSystem;
private ACSystemViewModel _acSystem;
private SecuritySystemViewModel _securitySystem;
public SmartHomeViewModel(
LightSystemViewModel lightSystem,
ACSystemViewModel acSystem,
SecuritySystemViewModel securitySystem)
{
_lightSystem = lightSystem;
_acSystem = acSystem;
_securitySystem = securitySystem;
// 创建组合命令
LeaveHomeCommand = new CompositeCommand();
// 注册子命令
LeaveHomeCommand.RegisterCommand(_lightSystem.TurnOffAllLightsCommand);
LeaveHomeCommand.RegisterCommand(_acSystem.TurnOffACCommand);
LeaveHomeCommand.RegisterCommand(_securitySystem.ActivateSecurityCommand);
}
}
xml
<!-- 一个按钮触发所有操作 -->
<Button Content="离家模式"
Command="{Binding LeaveHomeCommand}" />
四、最佳实践与场景
4.1 最佳实践原则
1. 使用懒加载初始化命令
csharp
private DelegateCommand _saveCommand;
public DelegateCommand SaveCommand =>
_saveCommand ?? (_saveCommand = new DelegateCommand(ExecuteSave, CanSave))
.ObservesProperty(() => Document) // 自动监听属性变化
.ObservesProperty(() => IsDirty);
2. 使用ObservesProperty自动更新命令状态
csharp
// 当SelectedItem变化时,自动重新评估CanExecute
private DelegateCommand _deleteCommand;
public DelegateCommand DeleteCommand =>
_deleteCommand ?? (_deleteCommand = new DelegateCommand(ExecuteDelete, CanDelete)
.ObservesProperty(() => SelectedItem));
3. 异步命令处理
csharp
using Prism.Commands;
private DelegateCommand _loadDataCommand;
public DelegateCommand LoadDataCommand =>
_loadDataCommand ?? (_loadDataCommand = new DelegateCommand(
async () => await ExecuteLoadDataAsync()
));
private async Task ExecuteLoadDataAsync()
{
IsLoading = true;
try
{
await Task.Delay(1000); // 模拟异步操作
Data = await _service.FetchDataAsync();
}
finally
{
IsLoading = false;
}
}
4.2 最佳使用场景
场景1:表单提交
csharp
public class OrderViewModel : BindableBase
{
public DelegateCommand SubmitOrderCommand { get; }
public OrderViewModel()
{
SubmitOrderCommand = new DelegateCommand(
async () => await SubmitOrderAsync(),
CanSubmitOrder
)
.ObservesProperty(() => CustomerName)
.ObservesProperty(() => SelectedProducts)
.ObservesProperty(() => IsValidAddress);
}
private bool CanSubmitOrder()
{
// 所有条件满足才能提交
return !string.IsNullOrEmpty(CustomerName) &&
SelectedProducts?.Any() == true &&
IsValidAddress &&
!IsSubmitting;
}
}
场景2:导航命令
csharp
public class MainViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
public DelegateCommand<string> NavigateCommand { get; }
public MainViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string viewName)
{
if (CanNavigate(viewName))
{
_regionManager.RequestNavigate("MainRegion", viewName);
}
}
private bool CanNavigate(string viewName)
{
// 检查权限、当前状态等
return _userService.HasAccessToView(viewName) &&
!IsBusy;
}
}
4.3 高级模式:命令工厂
csharp
// 1. 定义命令接口
public interface ICommandFactory
{
DelegateCommand CreateSaveCommand(Action execute, Func<bool> canExecute = null);
DelegateCommand<T> CreateParameterizedCommand<T>(Action<T> execute, Func<T, bool> canExecute = null);
}
// 2. 实现命令工厂
public class CommandFactory : ICommandFactory
{
public DelegateCommand CreateSaveCommand(Action execute, Func<bool> canExecute = null)
{
var command = new DelegateCommand(execute, canExecute);
// 添加公共行为,如日志、异常处理
return command;
}
public DelegateCommand<T> CreateParameterizedCommand<T>(
Action<T> execute, Func<T, bool> canExecute = null)
{
return new DelegateCommand<T>(
param =>
{
try
{
Log($"执行命令,参数: {param}");
execute(param);
}
catch (Exception ex)
{
HandleCommandException(ex);
}
},
canExecute
);
}
}
// 3. 在ViewModel中使用
public class ProductViewModel : BindableBase
{
private readonly ICommandFactory _commandFactory;
public DelegateCommand SaveCommand { get; }
public ProductViewModel(ICommandFactory commandFactory)
{
_commandFactory = commandFactory;
SaveCommand = _commandFactory.CreateSaveCommand(
ExecuteSave,
CanSave
);
}
}
五、常见陷阱与解决方案
陷阱1:内存泄漏
csharp
// ❌ 错误:匿名方法可能阻止垃圾回收
public DelegateCommand MyCommand => new DelegateCommand(() =>
{
// 这里捕获了this,可能导致内存泄漏
this.DoSomething();
});
// ✅ 正确:使用弱引用或标准模式
private DelegateCommand _myCommand;
public DelegateCommand MyCommand =>
_myCommand ?? (_myCommand = new DelegateCommand(ExecuteMyCommand));
private void ExecuteMyCommand()
{
// 明确的方法,没有隐藏的引用
}
陷阱2:异步命令异常处理
csharp
// 创建一个安全的异步命令包装器
public static class AsyncCommandExtensions
{
public static DelegateCommand ToAsyncCommand(
this Func<Task> asyncAction,
Action<Exception> errorHandler = null)
{
return new DelegateCommand(async () =>
{
try
{
await asyncAction();
}
catch (Exception ex)
{
errorHandler?.Invoke(ex);
// 或使用统一的错误处理
HandleException(ex);
}
});
}
}
// 使用
public DelegateCommand LoadDataCommand =>
_loadDataCommand ?? (_loadDataCommand =
(async () => await LoadDataAsync()).ToAsyncCommand());
六、总结
什么时候使用Prism命令?
| 场景 | 使用命令类型 | 说明 |
|---|---|---|
| 按钮点击触发业务逻辑 | DelegateCommand | 最常见场景 |
| 需要根据条件禁用UI | DelegateCommand with CanExecute | 表单验证、权限控制 |
| 多个操作同时执行 | CompositeCommand | 批量操作、工作流 |
| 带参数的操作 | DelegateCommand<T> | 增/减按钮、选择操作 |
| 异步长时间操作 | Async DelegateCommand | 数据加载、文件操作 |
核心价值
-
关注点分离:View只负责显示,ViewModel负责逻辑
-
可测试性:命令逻辑可以单独测试,无需UI
-
可重用性:相同的命令可以在多个View中重用
-
状态管理:CanExecute自动管理UI启用/禁用状态
-
松耦合:View不知道具体实现,便于维护和修改
Prism命令就像是UI和业务逻辑之间的"翻译官",它让两者能够顺畅沟通,但又保持适当的距离,这正是良好架构的关键所在。
原文地址:https://blog.csdn.net/leohu0723/article/details/156870214
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!
