Composite Commands(复合命令)
在大多数情况下,ViewModel定义的命令将绑定到View中的控件上,以便于用户可以从View中直接调用该命令。但是在有些情况中,你可以希望从应用程序UI父视图的控件中上调用一个或者多个ViewModel中的命令。
例如,如果你的应用允许用户同时编辑多个项目,你可能希望允许用户使用一个命令保存所有项目,该命令会由应用程序的工具栏或者功能区的一个按钮来表示。在这种情况下,SaveAll命令将会调用ViewModel实例中为每个项目所实现的保存命令。
Prism是通过CompositeCommand
类支持这种方案的。
CompositeCommand
类表示由多个子命令组成的命令。但调用复合命令时,将会依次调用每一个子命令。当您需要在UI中将一组命令表示为单个命令,或者希望调用多个命令来实现一个逻辑命令时,它将非常有用。
CompositeCommand
类维护一个子命令列表(DelegateCommand
实例)。CompositeCommand
类的Execute
方法只是依次调用每个子命令的Execute
方法。与此相同CanExecute
也是依次调用每一个子命令的CanExecute
。但是如果任意一个子命令无法被执行,CanExecute
方法将返回false
。换句话说,在默认情况下只有所有的子命令都可以执行时,CompositeCommand
才会执行。
[!注意]
CompositeCommand
能够在Prism.Core NuGet包中的Prism.Commands命名空间中找到。
创建一个复合命令
若要创建复合命令,请实例化一个CompositeCommand
实例,然后将其作为ICommand
或ComponsiteCommand
属性公开。
public class ApplicationCommands
{
private CompositeCommand _saveCommand = new CompositeCommand();
public CompositeCommand SaveCommand
{
get { return _saveCommand; }
}
}
如何使CompositeCommand全局可用
通常CompositeCommands会在整个应用程序中共享,并需要在全局范围内可用。重要的是,当你使用CompositeCommand
注册子命令时,你在整个应用中使用的是同一个CompositeCommand实例。这需要将CompositeCommand定义为应用程序中的单例。这可以通过依赖注入(DI)来实现,也可以通过将compositcommand定义为静态类来实现。
使用依赖注入
第一步是创建一个接口定义CompositeCommands。
public interface IApplicationCommands
{
CompositeCommand SaveCommand { get; }
}
接下来,创建一个实现该接口的类。
public class ApplicationCommands : IApplicationCommands
{
private CompositeCommand _saveCommand = new CompositeCommand();
public CompositeCommand SaveCommand
{
get { return _saveCommand; }
}
}
一旦定义了ApplicationCommands类,就必须在容器中将其注册为单例。
public partial class App : PrismApplication
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
}
}
接下来,请求ViewModel构造函数中的“IApplicationCommands”接口。一旦你有了`ApplicationCommands'类的实例,就可以立即使用相应的CompositeCommand注册DelegateCommands。
public DelegateCommand UpdateCommand { get; private set; }
public TabViewModel(IApplicationCommands applicationCommands)
{
UpdateCommand = new DelegateCommand(Update);
applicationCommands.SaveCommand.RegisterCommand(UpdateCommand);
}
使用静态类
创建一个表示CompositeCommands的静态类
public static class ApplicationCommands
{
public static CompositeCommand SaveCommand = new CompositeCommand();
}
在ViewModel中,将子命令与静态“ApplicationCommands”类相关联。
public DelegateCommand UpdateCommand { get; private set; }
public TabViewModel()
{
UpdateCommand = new DelegateCommand(Update);
ApplicationCommands.SaveCommand.RegisterCommand(UpdateCommand);
}
[!注意] 为了提高代码的可维护性和可测试性,建议您使用依赖项注入方法。
绑定到全局可用命令
创建CompositeCommands后,现在必须将它们绑定到UI元素,以便调用命令。
使用依赖注入
当使用依赖注入时,你必须暴露IApplicationCommands
用于绑定到View。在View的ViewModel中的构造函数中请求IApplicationCommands
,并为实例设置一个类型为IApplicationCommands
的属性。
public class MainWindowViewModel : BindableBase
{
private IApplicationCommands _applicationCommands;
public IApplicationCommands ApplicationCommands
{
get { return _applicationCommands; }
set { SetProperty(ref _applicationCommands, value); }
}
public MainWindowViewModel(IApplicationCommands applicationCommands)
{
ApplicationCommands = applicationCommands;
}
}
在View中,将按钮绑定到ApplicationCommands.SaveCommand
属性。其中的SaveCommand
是在ApplicationCommands
类中定义的属性。
<Button Content="Save" Command="{Binding ApplicationCommands.SaveCommand}"/>
使用静态类
如果您使用的是静态类方法,下面的代码示例将展示如何将按钮绑定到WPF中的静态类ApplicationCommands上。
<Button Content="Save" Command="{x:Static local:ApplicationCommands.SaveCommand}" />
取消注册命令
如前面的示例所示,子命令是使用CompositeCommand.RegisterCommand
方法注册的。但是,当您不再希望子命令响应CompositeCommand或正在销毁View/ViewModel以进行垃圾收集时,应使用CompositeCommand.UnregisterCommand
方法注销子命令。
public void Destroy()
{
_applicationCommands.UnregisterCommand(UpdateCommand);
}
[!重要] 当不再需要View/ViewModel(准备进行GC)时,您必须从
CompositeCommand
中取消注册您的命令。否则就会导致内存泄漏
在活动视图上执行命令
父视图级的复合命令通常用于协调子视图级的命令如何调用。在某些情况下,您可能希望执行所有显示视图的命令,如前面描述的Save all命令示例所示。在其他情况下,您可能希望只在活动视图上执行命令。在这种情况下,复合命令将只在被认为是活动的视图上执行子命令;它不会在不活动的视图上执行子命令。例如,您可能想要在应用程序的工具栏上实现一个放大命令,该命令只使当前活动的项被放大,如下图所示。
为了支持这种情况,Prism提供了IActiveAware
接口。IActiveAware
接口定义了一个IsActive
属性,当实现者处于活动状态时,该属性返回true
,而每当活动状态发生变化时,则会引发IsActiveChanged
事件。
您可以在Views或ViewModels上实现IActiveAware
接口。它主要用于跟踪View的活动状态。其中View是否处于活动状态取决于特定控件内的View。例如,对于Tab控件,有一个适配器可以将当前选定选项卡中的视图设置为活动的。
DelegateCommand
类也实现了IActiveAware
接口。通过在构造函数中为monitorCommandActivity
参数指定true
,可以配置CompositeCommand
来评估子DelegateCommands的活动状态(除了CanExecute
状态)。当此参数设置为true
时,在确定CanExecute
方法的返回值以及在Execute
方法中执行子命令时,CompositeCommand
类将考虑每个子DelegateCommand命令的活动状态。
public class ApplicationCommands : IApplicationCommands
{
private CompositeCommand _saveCommand = new CompositeCommand(true);
public CompositeCommand SaveCommand
{
get { return _saveCommand; }
}
}
当monitorCommandActivity
参数为true
时,CompositeCommand
类表现出以下行为:
CanExecute
:仅当所有活动命令都可以执行时才返回“true”。完全不会考虑处于非活动状态的子命令。Execute
:执行所有活动命令。不活动的子命令根本不会被考虑。
通过在你的ViewModels上实现IActiveAware
接口,当你的视图变为活动状态或非活动状态时,你会收到通知。当视图的活动状态发生变化时,可以更新子命令的活动状态。然后,当用户调用复合命令时,将调用活动子视图上的命令。
public class TabViewModel : BindableBase, IActiveAware
{
private bool _isActive;
public bool IsActive
{
get { return _isActive; }
set
{
_isActive = value;
OnIsActiveChanged();
}
}
public event EventHandler IsActiveChanged;
public DelegateCommand UpdateCommand { get; private set; }
public TabViewModel(IApplicationCommands applicationCommands)
{
UpdateCommand = new DelegateCommand(Update);
applicationCommands.SaveCommand.RegisterCommand(UpdateCommand);
}
private void Update()
{
//implement logic
}
private void OnIsActiveChanged()
{
UpdateCommand.IsActive = IsActive; //set the command as active
IsActiveChanged?.Invoke(this, new EventArgs()); //invoke the event for all listeners
}
}