Commanding(命令)
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();
}
}