【WPF】Prism学习(三)
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.Core
NuGet包中。
2.创建一个复合命令
复合命令是由多个子命令组成的命令,当复合命令被触发时,它的每个子命令会依次被执行。这在用户界面(UI)中表示一组命令为单个命令或者想要执行多个命令以实现一个逻辑命令时非常有用。
具体来说,创建复合命令的步骤:
- 实例化一个
CompositeCommand
对象。 - 将这个
CompositeCommand
对象作为一个属性暴露出来,这个属性可以是ICommand
或者CompositeCommand
类型。
下面是具体的代码示例:
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
实例。public interface IApplicationCommands { CompositeCommand SaveCommand { get; } }
-
实现接口:然后,创建一个类
ApplicationCommands
来实现这个接口,并在类中定义_saveCommand
作为CompositeCommand
的实例。public class ApplicationCommands : IApplicationCommands { private CompositeCommand _saveCommand = new CompositeCommand(); public CompositeCommand SaveCommand { get => _saveCommand; } }
-
注册为单例:在你的应用程序中,需要将
ApplicationCommands
类注册为单例,这样在整个应用程序中使用的都是同一个CompositeCommand
实例。public partial class App : PrismApplication { protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>(); } }
-
在ViewModel中注册子命令:在ViewModel的构造函数中,请求
IApplicationCommands
接口,并使用SaveCommand
来注册你的DelegateCommand
。public DelegateCommand UpdateCommand { get; private set; } public TabViewModel(IApplicationCommands applicationCommands) { UpdateCommand = new DelegateCommand(Update); applicationCommands.SaveCommand.RegisterCommand(UpdateCommand); }
3.2.使用静态类来全局使用复合命令
-
创建静态类:创建一个静态类
ApplicationCommands
,并在其中定义一个静态的SaveCommand
属性,它是一个CompositeCommand
实例。public static class ApplicationCommands { public static CompositeCommand SaveCommand = new CompositeCommand(); }
-
在ViewModel中关联子命令:在ViewModel中,将你的
DelegateCommand
与静态的ApplicationCommands
类关联起来。public 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
的属性。public 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
类中定义的。<Button Content="Save" Command="{Binding ApplicationCommands.SaveCommand}"/>
这里,按钮的
Command
属性被绑定到视图模型中的SaveCommand
属性,当按钮被点击时,会触发SaveCommand
。
4.2.使用静态类(Static Class)
-
绑定到静态ApplicationCommands类:如果你使用的是静态类方法,以下代码示例展示了如何在WPF中将按钮绑定到静态的
ApplicationCommands
类。<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
持有,它们将不会被垃圾回收,从而导致内存泄漏。内存泄漏是指程序中已分配的内存空间由于某种原因未被正确释放,导致随着时间的推移,可用内存越来越少,最终可能影响程序的性能。
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
的活跃状态。
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. 示例代码:
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)
- 命令(Commanding)
- 复合命令(Composite Commands)