自学内容网 自学内容网

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:组合命令 - 智能家居场景

想象一个"离家模式"按钮,需要同时:

  1. 关闭所有灯

  2. 关闭空调

  3. 启动安防系统

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最常见场景
需要根据条件禁用UIDelegateCommand with CanExecute表单验证、权限控制
多个操作同时执行CompositeCommand批量操作、工作流
带参数的操作DelegateCommand<T>增/减按钮、选择操作
异步长时间操作Async DelegateCommand数据加载、文件操作

核心价值

  1. 关注点分离:View只负责显示,ViewModel负责逻辑

  2. 可测试性:命令逻辑可以单独测试,无需UI

  3. 可重用性:相同的命令可以在多个View中重用

  4. 状态管理:CanExecute自动管理UI启用/禁用状态

  5. 松耦合:View不知道具体实现,便于维护和修改

Prism命令就像是UI和业务逻辑之间的"翻译官",它让两者能够顺畅沟通,但又保持适当的距离,这正是良好架构的关键所在。


原文地址:https://blog.csdn.net/leohu0723/article/details/156870214

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!