欢迎光临
我们一直在努力

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和业务逻辑之间的"翻译官",它让两者能够顺畅沟通,但又保持适当的距离,这正是良好架构的关键所在。

     

    赞(0)
    未经允许不得转载:171主机测评 » Prism 命令详解:从遥控器到智能家居的编程哲学
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址