csharpshare.com
Show / Hide Table of Contents

Commanding(命令)

Gitee仓库 Prism-Documentation-CH 时间 2023-1-16

ViewModel除了提供在视图中显示和修改数据的功能外,还可以定义一个或者多个可由用户执行的操作。其中用户对UI的操作通常定义为命令。命令会为用户的操作提供了一个简便的方式来表示,并很容易绑定在UI的控件中。其中封装了用户操作实际的代码,使其与视图中的展示效果保持分离。

命令可以以很多不同的方式在用户和视图交互时是进行显示和调用。在大多数的情境下,命令会由鼠标点击所调用;另外也可以由快捷键、触摸手势和其他的输入方式所调用。这是由于View中的控件绑定了ViewModel中的命令,所以命令可以被用户使用任何控制器定义的输入方式或手势所调用。View中的ui控件与命令之间是可以双向绑定的,在这种情况下用户与ui交互调用命令时,ui控件可以跟随命令自动的启用和禁用。

ViewModel可以将命令声明为命令对象(实现了ICommand接口的对象)。该对象可以声明定义View和命令的交互,而不需要在View的代码文件中使用复杂的事件进行处理。例如,某些控件自带支持命令并提供一个Command的属性,该属性可以将数据绑定到一个由ViewModel提供的ICommand对象;在其他情况下,Command Behavior可以将控件与ViewModel的命令方法或命令对象相关联。(译者注:其他情况下通常在View中引用xmlns:i="http://schemas.microsoft.com/xaml/behaviors")

实现ICommand接口很简单,Prism提供了该接口的DelegateCommand实现,你可以随时在应用程序中使用。

[!注意] DelegateCommand位于Prism.Core Nuget包中的Prism.Commands命名空间中。

创建DelegateCommand

Prism DelegateCommand类中封装了两个委托,每个委托在你的ViewModel类中实现方法。DelegateCommand通过调用这些委托来实现ICommand接口中的Execute和CanExecute方法。你可以在DelegateCommand类的构造函数中指定ViewModel方法的委托。例如,以下代码展示了一个名为Submite Command的DelegateCommand实例是如何由ViewModel中OnSubmit和CanSubmit方法的委托进行构建的,然后该命令返回了一个暴露了仅读取的属性的DelegateCommand引用。

public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand<object>(Submit, CanSubmit);
    }

    void Submit(object parameter)
    {
        //implement logic
    }

    bool CanSubmit(object parameter)
    {
        return true;
    }
}

当在delegateCommand对象上调用执行方法时,它只需通过你在构造函数中指定的委托便将调用转给ViewModel类的方法。同样,当CanExecute方法被调用时也会调用ViewMoel类中相应的方法。CanExecute方法的委托是可选的,如果你没有指定则始终返回true。

DelegateCommand类是一个泛型类型。其类型参数指定了传递Execute和CanExecute方法的命令参数类型。在前面的示例中,命令参数为object类型。当不需要命令参数时,Prism还提供了DelegateCommand的non-generic版本,如下所示:

public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit, CanSubmit);
    }

    void Submit()
    {
        //implement logic
    }

    bool CanSubmit()
    {
        return true;
    }
}

[!注意] DelegateCommand有意的阻止了值类型(int、double和bool等)的使用。因为ICommand采用了object的参数,当有一个值类型的T会在XAML初始化绑定命令调用CanExecute(null)时会导致报错。使用default(T)作为解决方案是可以考虑和拒绝的,因为编译器无法区分有效值和默认值。如果你想使用值类型作为参数,你必须通过DelegateCommand<Nullable<int>>或者简化的?句法 (DelegateCommand<int?>)使其可空。

在View调用DelegateCommand

这里提供了将View中的控件和ViewModel提供的命令对象相关联的多种方式,某些WPF、Xamarin.Forms和UWP控件也可以通过Command属性轻松地绑定到命令对象。

<Button Command="{Binding SubmitCommand}" CommandParameter="OrderId"/>

命令参数也可以选择使用CommandParameter属性定义。预期参数类型在DelegateCommand<T>泛型声明中指定。当用户和控件交互时,该控件将自动调用目标命令,以及如果提供了命令参数时也将传递给Execute方法。在此前的示例中,点击按钮时将自动调用SubmitCommand。此外,如果指定了CanExecute委派,按钮将在CanExecute返回false时禁用,返回true时启用。

引发更改通知

ViewModel经常需要指示命令中CanExecute状况的变化,以使UI中的任何绑定到命令的控件都会更新其启用状态,从而反映绑定命令的可用性。DelegateCommand提供了几种方法来发送这些通知给UI。

RaiseCanExecuteChanged

每当需要手动更新绑定得UI元素状态时,请使用RaiseCanExecuteChanged方法。例如,当IsEnabled更改时,我们在属性的setter中调用RaiseCanExecuteChanged来通知UI状态的更改。

        private bool _isEnabled;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                SetProperty(ref _isEnabled, value);
                SubmitCommand.RaiseCanExecuteChanged();
            }
        }

ObservesProperty

如果需要命令应在属性值更改时发送通知,你可以使用ObservesProperty方法。在使用ObservesProperty方法时,每当提供属性的值改变,DelegateCommand将自动调用RaiseCanExecuteChanged通知UI状态的更改。

public class ArticleViewModel : BindableBase
{
    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set { SetProperty(ref _isEnabled, value); }
    }

    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit, CanSubmit).ObservesProperty(() => IsEnabled);
    }

    void Submit()
    {
        //implement logic
    }

    bool CanSubmit()
    {
        return IsEnabled;
    }
}

[!注意] 你可以在使用ObservesProperty方法时链式注册多个属性进行观察。例如:ObservesProperty(() => IsEnabled).ObservesProperty(() => CanSave)。

ObservesCanExecute

如果你的CanExecute返回结果仅是一个Boolean属性,你可以使用ObservesCanExecute替代CanExecute委托的声明。 ObservesCanExecute不仅在注册属性改变时通知UI,也使用和CanExecute同样的属性值。

public class ArticleViewModel : BindableBase
{
    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set { SetProperty(ref _isEnabled, value); }
    }

    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit).ObservesCanExecute(() => IsEnabled);
    }

    void Submit()
    {
        //implement logic
    }
}

[!警告] 不要尝试链式注册ObservesCanExecute方法,只有一个属性能被作为CanExcute委托所观测。

声明一个基于Task的DelegateCommand

在目前的情况中,在Execute委托中使用async/await调用异步方法是十分普遍的要求。很多人第一个想法是他们需要AsyncCommand,但是这个想法是错误的。ICommand本质上是同步的,并且Execute和CanExecute应该被视作事件。这意味着async void是一个完美且有效的语法。下列有两种方法在DelegateCommand中使用异步方法。

Option 1:

public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit);
    }

    async void Submit()
    {
        await SomeAsyncMethod();
    }
}

Option 2:

public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(async ()=> await Submit());
    }

    Task Submit()
    {
        return SomeAsyncMethod();
    }
}
本文导航
返回顶部 ©2022-2023 csharpshare.com    冀ICP备2022026743号-1     公安备案图标 冀公网安备 13052902000206号     Icons by Icons8