WPF/MVVM系列(5)——命令

原文地址:李浩的博客 lihaohello.top


学习WPF的命令机制,我认为最重要的是能够找到一种在MVVM模式中使用它的最佳工程实践方式。
绑定和命令是MVVM模式的核心,后面介绍MVVM模式基本是顺水推舟了。

与路由事件的关系

在深入了解WPF的命令之前,有必要理清命令和路由事件的关系:
(1)路由事件提供了一种灵活的事件传递机制,主要用于处理界面状态的变化;
(2)命令主要用于封装程序的操作逻辑,从而保证了操作逻辑的复用性。
(3)路由事件和命令是两套出发点不同的机制,不是相互替换的关系。在大型软件系统中,通常需要路由事件和命令相互协作来完成复杂功能。

使用RoutedCommand

在WPF中,比较典型的是按钮控件,它有Command属性,设置这个属性就可以绑定命令。
一种方式是使用RoutedCommand类,这是标准库提供的实现了ICommand接口的命令类,但是它没有实现CanExecute()和Executed()逻辑,需要通过命令关联的方式添加该逻辑。
这种方式需要使用命令关联统一定义命令的逻辑,每个命令没有形成清晰的封闭整体,不是推荐用法,不详细介绍。

较好的工程实践

回归命令机制的初衷,开发者不希望在窗体的事件处理函数中写业务逻辑,而是倾向于将每个业务逻辑写成普通函数,然后包装成命令供界面控件使用。
那么,如何将业务处理逻辑包装成命令呢?我认为以下方式是比较好的实践思路。
(1)自定义以下命令类,其构造函数可以传入实际的Execute()和CanExecute()执行逻辑,开发者显式调用RaiseCanExecuteChange()函数可以触发CanExecute()的执行,从而更新控件的状态,一般可以在窗体的事件处理器中调用。

public class MyCommand : ICommand {
    private Action<object> execute;
    private Func<object, bool> canExecute;

    public MyCommand(Action<object> execute, Func<object, bool> canExecute = null) {
        this.execute = execute;
        this.canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter) {
        if (canExecute != null)
            return CanExecute(parameter);
        return true;
    }

    public void Execute(object parameter) {
        execute?.Invoke(parameter);
    }

    public void RaiseCanExecuteChange() {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

(2)在ViewModel类中定义命令,并用属性将其包装供View绑定,这里采用的是经典的DataContext方式。

public class TestViewModel : NotifyPropertyChanged {
    private MyCommand _command1;
    public MyCommand Command1 {
        get {
            return _command1;
        }
        set {
            _command1 = value;
            RaisePropertyChanged();
        }
    }

    public TestViewModel() {
        _command1 = new MyCommand(ShowMessage, SetEnbale);
    }

     #region 业务处理逻辑,程序的核心
     private void ShowMessage(object parameter) {
         var para = parameter as Window;
         if (para != null)
             MessageBox.Show($"窗体宽度为:{para.Width}");
     }
    
     private bool SetEnbale(object parameter) {
         var para = parameter as Window;
         if (para != null && para.Width > 300)
             return true;
         return false;
     }
     #endregion
}
<Window x:Class="TestMvvm.Views.Test"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestMvvm.Views"
        mc:Ignorable="d"
        x:Name="myWin"
        SizeChanged="myWin_SizeChanged"
        Title="Test" SizeToContent="WidthAndHeight">

    <StackPanel Orientation="Vertical">
        <Button x:Name="btn1" Content="按钮1" Command="{Binding Command1}"
                CommandParameter="{Binding ElementName=myWin}"/>
    </StackPanel>
</Window>

以上代码有几个关键点:

  • 传参:这里在XAML中通过绑定将窗体传递到命令,CanExecute()和Execute()都能获得这个参数。
  • 更新按钮状态:本例子想在窗体宽度大于300时更新按钮状态为可见,点击按钮后弹窗显示窗体宽度。为了更新按钮状态,必须在窗体尺寸变化函数中调用RaiseCanExecuteChange()函数。不直接调用CanExecute()函数,是因为开发者没有办法显式向其传递参数。

原文地址:李浩的博客 lihaohello.top

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值