Prism Commands

1.复合命令(Composite Commanding)
这段内容主要介绍了在应用程序中如何使用复合命令(Composite Commands)来实现多个视图模型(ViewModels)上的命令。以下是对这段内容的解释:
1.1. 复合命令的概念:
- 在许多情况下,ViewModel中定义的命令会绑定到相关视图中的控件上,这样用户就可以直接在视图中调用这些命令。
- 然而,在某些情况下,你可能希望从一个父视图中的控件调用一个或多个ViewModel上的命令。
1.2. 复合命令的应用场景:
- 例如,如果你的应用程序允许用户同时编辑多个项目,你可能希望允许用户使用应用程序工具栏或功能区中的单个按钮来保存所有项目。在这种情况下,"保存全部"命令将调用每个项目的ViewModel实例实现的"保存"命令。

1.3. Prism框架对复合命令的支持:
- Prism框架通过
CompositeCommand类支持这种场景。 CompositeCommand类表示由多个子命令组成的命令。当复合命令被调用时,会依次调用每个子命令。- 它适用于需要在UI中将一组命令表示为单个命令的情况,或者当你想要调用多个命令以实现一个逻辑命令时。
1.4. CompositeCommand类的工作原理:
CompositeCommand类维护一个子命令列表(DelegateCommand实例)。CompositeCommand类的Execute方法简单地依次调用每个子命令的Execute方法。CanExecute方法类似地调用每个子命令的CanExecute方法,但如果任何一个子命令不能执行,CanExecute方法将返回false。换句话说,默认情况下,只有当所有子命令都可以执行时,CompositeCommand才能被执行。
1.5. CompositeCommand类的位置:
CompositeCommand可以在Prism.Commands命名空间中找到,该命名空间位于Prism.CoreNuGet包中。
2.创建一个复合命令
复合命令是由多个子命令组成的命令,当复合命令被触发时,它的每个子命令会依次被执行。这在用户界面(UI)中表示一组命令为单个命令或者想要执行多个命令以实现一个逻辑命令时非常有用。
具体来说,创建复合命令的步骤:
- 实例化一个
CompositeCommand对象。 - 将这个
CompositeCommand对象作为一个属性暴露出来,这个属性可以是ICommand或者CompositeCommand类型。
下面是具体的代码示例:
csharp
public class ApplicationCommands
{
// 创建一个私有的 CompositeCommand 实例
private CompositeCommand _saveCommand = new CompositeCommand();
// 将这个实例作为一个公共属性暴露出来,允许外部访问和使用这个复合命令
public CompositeCommand SaveCommand
{
get => _saveCommand;
}
}
在这个例子中,ApplicationCommands 类包含了一个名为 SaveCommand 的属性,这个属性是一个 CompositeCommand 类型的实例。这样,你就可以在应用程序的任何地方通过 ApplicationCommands.SaveCommand 来访问和使用这个复合命令,例如,将其绑定到用户界面的按钮上,当按钮被点击时,就会触发这个复合命令及其所有的子命令。
3. 全局使用复合命令(CompositeCommand)
3.1.使用依赖注入(DI)来全局使用复合命令
-
定义接口 :首先,你需要定义一个接口
IApplicationCommands,该接口包含一个SaveCommand属性,它是一个CompositeCommand实例。csharppublic interface IApplicationCommands { CompositeCommand SaveCommand { get; } } -
实现接口 :然后,创建一个类
ApplicationCommands来实现这个接口,并在类中定义_saveCommand作为CompositeCommand的实例。csharppublic class ApplicationCommands : IApplicationCommands { private CompositeCommand _saveCommand = new CompositeCommand(); public CompositeCommand SaveCommand { get => _saveCommand; } } -
注册为单例 :在你的应用程序中,需要将
ApplicationCommands类注册为单例,这样在整个应用程序中使用的都是同一个CompositeCommand实例。csharppublic partial class App : PrismApplication { protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>(); } } -
在ViewModel中注册子命令 :在ViewModel的构造函数中,请求
IApplicationCommands接口,并使用SaveCommand来注册你的DelegateCommand。csharppublic DelegateCommand UpdateCommand { get; private set; } public TabViewModel(IApplicationCommands applicationCommands) { UpdateCommand = new DelegateCommand(Update); applicationCommands.SaveCommand.RegisterCommand(UpdateCommand); }
3.2.使用静态类来全局使用复合命令
-
创建静态类 :创建一个静态类
ApplicationCommands,并在其中定义一个静态的SaveCommand属性,它是一个CompositeCommand实例。csharppublic static class ApplicationCommands { public static CompositeCommand SaveCommand = new CompositeCommand(); } -
在ViewModel中关联子命令 :在ViewModel中,将你的
DelegateCommand与静态的ApplicationCommands类关联起来。csharppublic DelegateCommand UpdateCommand { get; private set; } public TabViewModel() { UpdateCommand = new DelegateCommand(Update); ApplicationCommands.SaveCommand.RegisterCommand(UpdateCommand); }
为了提高代码的可维护性和可测试性,推荐使用依赖注入的方式而不是静态类。
4.绑定全局可用的复合命令(CompositeCommands)
4.1.使用依赖注入(Dependency Injection)
-
暴露IApplicationCommands :在使用依赖注入(DI)时,你需要在视图模型(ViewModel)中暴露
IApplicationCommands接口,以便将其绑定到视图(View)。 -
设置属性 :在视图模型的构造函数中请求
IApplicationCommands实例,并设置一个类型为IApplicationCommands的属性。csharppublic class MainWindowViewModel : BindableBase { private IApplicationCommands _applicationCommands; public IApplicationCommands ApplicationCommands { get => _applicationCommands; set => SetProperty(ref _applicationCommands, value); } public MainWindowViewModel(IApplicationCommands applicationCommands) { ApplicationCommands = applicationCommands; } }在这个例子中,
MainWindowViewModel类有一个ApplicationCommands属性,它在构造函数中被设置为传入的IApplicationCommands实例。 -
在视图中绑定按钮 :在XAML视图中,将按钮的
Command属性绑定到ApplicationCommands.SaveCommand属性。SaveCommand是在ApplicationCommands类中定义的。xml<Button Content="Save" Command="{Binding ApplicationCommands.SaveCommand}"/>这里,按钮的
Command属性被绑定到视图模型中的SaveCommand属性,当按钮被点击时,会触发SaveCommand。
4.2.使用静态类(Static Class)
-
绑定到静态ApplicationCommands类 :如果你使用的是静态类方法,以下代码示例展示了如何在WPF中将按钮绑定到静态的
ApplicationCommands类。xml<Button Content="Save" Command="{x:Static local:ApplicationCommands.SaveCommand}" />在这个例子中,按钮的
Command属性直接绑定到ApplicationCommands类中的静态SaveCommand属性。
5.从CompositeCommand中注销子命令
在编程中,特别是在使用命令模式(Command Pattern)时,我们可能会创建一些命令,并将它们注册到一个复合命令(CompositeCommand)中。这样做的好处是可以将多个命令作为一个单一的命令来处理,简化了用户界面(UI)的操作。
然而,当你的视图(View)或视图模型(ViewModel)不再需要时,比如它们即将被垃圾回收器(Garbage Collector, GC)回收,你应该从CompositeCommand中注销这些子命令。这是因为如果这些子命令仍然被CompositeCommand持有,它们将不会被垃圾回收,从而导致内存泄漏。内存泄漏是指程序中已分配的内存空间由于某种原因未被正确释放,导致随着时间的推移,可用内存越来越少,最终可能影响程序的性能。
csharp
public void Destroy()
{
_applicationCommands.UnregisterCommand(UpdateCommand);
}
在这段内容中,提供了一个Destroy方法的示例,该方法使用CompositeCommand.UnregisterCommand方法来注销一个名为UpdateCommand的子命令。这样做可以确保当View或ViewModel不再需要时,相关的命令可以被正确地从CompositeCommand中移除,从而允许垃圾回收器回收这些对象,避免内存泄漏。
6.执行活跃视图(Active Views)上的命令
6.1. 在父视图级别协调子视图命令的执行:
- 在某些情况下,你可能希望执行所有显示视图上的命令,例如前面提到的"保存全部"(Save All)命令。
- 在其他情况下,你可能只希望在当前活跃的视图上执行命令。在这种情况下,组合命令(CompositeCommand)只会在被认为是活跃的视图上执行子命令,而不活跃的视图上的子命令则不会执行。例如,你可能想在应用程序的工具栏上实现一个"缩放"(Zoom)命令,这个命令只会导致当前活跃的项目被缩放。

6.2. IActiveAware接口:
- 为了支持上述场景,Prism提供了
IActiveAware接口。该接口定义了一个IsActive属性,当实现者处于活跃状态时返回true,以及一个IsActiveChanged事件,每当活跃状态改变时触发。
6.3. 在视图或视图模型上实现IActiveAware接口:
- 这个接口主要用于跟踪视图的活跃状态。一个视图是否活跃是由特定控件内的视图决定的。例如,在Tab控件中,有一个适配器将当前选中的标签页中的视图设置为活跃状态。
6.4. DelegateCommand类实现IActiveAware接口:
CompositeCommand可以通过在构造函数中指定monitorCommandActivity参数为true来配置,以评估子DelegateCommand的活跃状态(除了CanExecute状态)。当这个参数设置为true时,CompositeCommand类在确定CanExecute方法的返回值以及在Execute方法中执行子命令时,会考虑每个子DelegateCommand的活跃状态。
csharp
public class ApplicationCommands : IApplicationCommands
{
private CompositeCommand _saveCommand = new CompositeCommand(true);
public CompositeCommand SaveCommand
{
get => _saveCommand;
}
}
6.5. CompositeCommand的行为:
- 当
monitorCommandActivity参数为true时,CompositeCommand类表现出以下行为:CanExecute:只有在所有活跃的命令都可以执行时才返回true。不活跃的子命令将完全不被考虑。Execute:执行所有活跃的命令。不活跃的子命令将完全不被考虑。
6.6. 在ViewModels上实现IActiveAware接口:
- 通过在ViewModels上实现
IActiveAware接口,当视图变为活跃或不活跃时,你将得到通知。当视图的活跃状态改变时,你可以更新子命令的活跃状态。然后,当用户调用组合命令时,活跃子视图上的命令将被调用。
6.7. 示例代码:
csharp
public class TabViewModel : BindableBase, IActiveAware
{
private bool _isActive;
public bool IsActive
{
get { return _isActive; }
set => SetProperty(ref _isActive, 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()
{
//实现逻辑
}
private void OnIsActiveChanged()
{
UpdateCommand.IsActive = IsActive; //set the command as active
IsActiveChanged?.Invoke(this, new EventArgs()); //invoke the event for all listeners
}
}
- 提供了一个
TabViewModel的示例,展示了如何实现IActiveAware接口,并在视图的活跃状态改变时更新子命令的活跃状态。
相关链接
- 介绍(Introduction)
- 命令(Commands)