csharpshare.com
Show / Hide Table of Contents

Composite Commands(复合命令)

Gitee仓库 Prism-Documentation-CH 时间 2023-1-12

在大多数情况下,ViewModel定义的命令将绑定到View中的控件上,以便于用户可以从View中直接调用该命令。但是在有些情况中,你可以希望从应用程序UI父视图的控件中上调用一个或者多个ViewModel中的命令。

例如,如果你的应用允许用户同时编辑多个项目,你可能希望允许用户使用一个命令保存所有项目,该命令会由应用程序的工具栏或者功能区的一个按钮来表示。在这种情况下,SaveAll命令将会调用ViewModel实例中为每个项目所实现的保存命令。

SaveAll复合命令

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
        }
    }
本文导航
返回顶部 ©2022-2023 csharpshare.com    冀ICP备2022026743号-1     公安备案图标 冀公网安备 13052902000206号     Icons by Icons8