Commanding
除了提供对要在视图中显示或编辑的数据的访问之外,ViewModel还可能定义可由用户执行的一个或多个动作或操作。用户可以通过UI执行的动作或操作通常被定义为命令。命令提供了一种方便的方法来表示可以轻松绑定到UI中的控件的操作或操作。它们封装了实现操作或操作的实际代码,并有助于使其与视图中的实际可视化表示分离。
当用户与视图交互时,用户可以以多种不同的方式直观地表示和调用命令。在大多数情况下,它们是通过鼠标单击调用的,但也可以通过快捷键按下,触摸手势或任何其他输入事件来调用它们。视图中的控件是绑定到ViewModel命令的数据,以便用户可以使用控件定义的任何输入事件或手势来调用它们。视图中的UI控件与命令之间的交互可以是双向的。在这种情况下,可以在用户与UI交互时调用该命令,并且可以在启用或禁用基础命令时自动启用或禁用UI。
ViewModel可以将命令实现为命令对象(实现ICommand
接口的对象)。可以以声明方式定义视图与命令的交互,而无需在视图的代码隐藏文件中使用复杂的事件处理代码。例如,某些控件固有地支持命令并提供Command
可以是绑定到ICommand
ViewModel提供的对象的数据的属性。在其他情况下,命令行为可用于将控件与ViewModel提供的命令方法或命令对象相关联。
实现ICommand
界面很简单。Prism提供了DelegateCommand
这个界面的实现,您可以在应用程序中轻松使用它。
[Using Delegate Commands视频教程](Prism.assets/Prism - Using Delegate Commands.mp4)
DelegateCommand
Prism DelegateCommand
类封装了两个委托,每个委托引用在ViewModel类中实现的方法。它通过调用这些委托来实现ICommand
接口Execute
和CanExecute
方法。您可以在DelegateCommand
类构造函数中指定ViewModel方法的委托。例如,以下代码示例显示如何DelegateCommand
通过指定OnSubmit和CanSubmit ViewModel方法的委托来构造表示Submit命令的实例。然后,该命令通过只读属性公开给视图,该属性返回对该参数的引用DelegateCommand
。
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand
在DelegateCommand
对象上调用Execute方法时,它只是通过您在构造函数中指定的委托将调用转发到ViewModel类中的方法。同样,CanExecute
调用该方法时,将调用ViewModel类中的相应方法。CanExecute
构造函数中方法的委托是可选的。如果没有指定一个委托,DelegateCommand
将始终返回true
了CanExecute
。
该DelegateCommand
班是一个泛型类型。type参数指定传递给Execute
和CanExecute
方法的命令参数的类型。在前面的示例中,command参数是type object
。非通用的版本DelegateCommand
类也通过棱镜用于提供当没有所需的命令参数,并且被定义为如下:
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,双,布尔等)。因为ICommand
需要一个object
具有值类型T
将导致当意外行为CanExecute(null)
XAML初始化命令绑定期间被调用。使用default(T)
被认为是被拒绝作为解决方案,因为实现者无法区分有效值和默认值。如果要将值类型用作参数,则必须使用DelegateCommand
或使用简写?
语法(DelegateCommand
)使其可为空。
从View调用DelegateCommands
有许多方法可以将视图中的控件与ViewModel提供的命令对象相关联。某些WPF,Xamarin.Forms和UWP控件可以通过Command
属性轻松地绑定到命令对象。
也可以使用CommandParameter
属性选择性地定义命令参数。期望参数的类型在DelegateCommand
泛型声明中指定。当用户与该控件交互时,控件将自动调用目标命令,并且命令参数(如果提供)将作为参数传递给命令的Execute
方法。在前面的示例中,按钮将SubmitCommand
在单击时自动调用。此外,如果CanExecute
指定了委托,则CanExecute
返回时将自动禁用该按钮,如果返回false
则将启用该按钮true
。
Raising Change Notifications
ViewModel通常需要指示命令CanExecute
状态的更改,以便UI中绑定到该命令的任何控件都将更新其启用状态以反映绑定命令的可用性。在DelegateCommand
提供了几种这些通知发送到用户界面。
RaiseCanExecuteChanged
RaiseCanExecuteChanged
每当需要手动更新绑定的UI元素的状态时,请使用该方法。例如,当IsEnabled
属性值更改时,我们调用RaiseCanExecuteChanged
属性的setter来通知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
属性的结果,则可以省去声明CanExecute
委托,并使用该ObservesCanExecute
方法。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, CanSubmit).ObservesCanExecute(() => IsEnabled);
}
void Submit()
{
//implement logic
}
}
警告
不要尝试链式注册ObservesCanExecute
方法。CanExcute
代表只能观察到一个属性。
基于Task-Based的DelegateCommand
async
/await
调用Execute
委托内部的异步方法是一个非常常见的要求。每个人的第一直觉是他们需要一个AsyncCommand
,但这种假设是错误的。ICommand
本质上是同步的,并且代表Execute
和CanExecute
代表应被视为事件。这意味着这async void
是一个非常有效的语法用于命令。使用异步方法有两种方法DelegateCommand
。
- 方法一
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand(Submit);
}
async void Submit()
{
await SomeAsyncMethod();
}
}
- 方法二
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand(async ()=> await Submit());
}
Task Submit()
{
return SomeAsyncMethod();
}
}