65.9K
CodeProject 正在变化。 阅读更多。
Home

Nito.MVVM (WPF) 库:命令

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2009 年 7 月 21 日

BSD

8分钟阅读

viewsIcon

59538

downloadIcon

586

介绍开源 Nito.MVVM (WPF) 库中的 ViewModel 命令类,并提供其使用指南。

引言

Nito.MVVM (WPF) 库包含多个命令类,在编写非依赖项对象的 ViewModel 类时非常有用。Nito.MVVM 没有为所有场景提供一个通用的 ICommand 实现,而是提供了许多命令类,每个类都满足一系列用例。特定用例的需求决定了应使用哪个命令类。

Nito.MVVM 命令类用于 ViewModel 命令,而非 GUI 命令。WPF 附带了许多常用的 GUI 命令,例如 CopyPasteBrowseForward。总的来说,GUI 命令操作的是 View 对象或状态,而不是 Model 对象。GUI 命令使用 RoutedCommand 实现,这是一种路由通过 View 元素树的命令对象。

ViewModel 命令操作 Model 对象,并通过 ViewModel 暴露给 View 进行绑定。它们封装了特定于域的逻辑,例如 SaveClientGenerateInvoice。Nito.MVVM 库中的命令类有助于编写需要暴露命令的 ViewModel 类。可以使用 RoutedCommand 实现 ViewModel 命令,但这效率低下且难以扩展。

命令类型和 CanExecuteChanged 原因

ViewModel 命令可以沿两个轴分组:命令类型CanExecuteChanged 原因。共有三种命令类型:

  1. 委托命令 - 一个独立的普通命令,不依赖于其他命令。从概念上讲,它只有代码,尽管这些代码可以调用其他命令。
  2. 复合命令 - 一个完全由其他(不相关的)命令组成、不带额外代码的命令。从概念上讲,它有一个有序的子命令列表。
  3. 多命令 - 一个由子对象列表中的相同命令组成的命令。从概念上讲,它有一个无序的子集,并带有一个命令选择器字符串。例如,SaveAllCommand 可以定义为每个子 ViewModel 的 SaveCommand

大多数 ViewModel 命令是调用 Model 对象上的方法的委托命令。复合命令不太常见。多命令在多选场景中很有用。复合命令和多命令都可以在其子命令列表中包含任何类型的命令。

命令必须通知侦听者其 CanExecute 返回值是否可能已更改。此事件可能由不同的情况引起;有五种CanExecuteChanged 源

  1. 静态 - 一个始终可以运行的命令。ICommand.CanExecute 始终返回 true,并且 ICommand.CanExecuteChanged 永远不会引发。此源在实践中很少见,通常是某种形式的 New 命令。
  2. 自动 - 一个命令的可运行性由 View 状态决定。自动命令将其 ICommand.CanExecuteChanged 事件与 CommandManager.RequerySuggested 绑定,后者在重要的用户输入事件时触发。因此,它们似乎“自动”引发此事件,故名“自动”。如果 Model 不会生成非用户发起的更改,则自动命令也可以用于依赖于 Model 状态的命令;但这很难扩展,尽管比使用 RoutedCommand 要好。
  3. ChildCanExecuteChanged - 一个命令的可运行性完全取决于其子命令的 ICommand.CanExecute 结果。这是复合命令和多命令的正常源。
  4. 多订阅 - 一个命令的可运行性可能因其他对象的事件而改变,例如 INotifyPropertyChanged.PropertyChanged 和/或 INotifyCollectionChanged.CollectionChanged。如果 Model 对象实现 INotifyPropertyChanged,则此命令源对于由 Model 状态决定的可运行性命令很有用。
  5. 手动 - 一个具有定义其可运行性的特殊要求的命令。

每种命令类型可能具有不同的CanExecuteChanged 原因

  1. 委托命令可以是静态的、自动的、多订阅的或手动的。通常,委托命令是多订阅的,但为了简单起见(以牺牲效率为代价)也可以是自动的。
  2. 复合命令通常是 ChildCanExecuteChanged。但是,如果其任何子命令是自动的,则复合命令可能出于优化目的而自动。
  3. 多命令通常是 ChildCanExecuteChanged。与复合命令一样,如果其任何子命令是自动的,则多命令可能出于优化目的而自动。

Nito.MVVM 中的命令类

Nito.MVVM 提供了几个命令类。带类型参数的类使用该参数作为传递给命令的参数类型。每种类型都旨在涵盖一个常见的用例:

  1. StaticCommand/StaticCommand<T> - 静态委托命令。
  2. AutomaticCommand/AutomaticCommand<T> - 自动委托命令。
  3. ManualCommand/ManualCommand<T> - 手动委托命令或多订阅委托命令。
  4. CompositeCommand - ChildCanExecute 复合命令。
  5. AutomaticCompositeCommand - 自动复合命令或自动多命令。
  6. MultiCommand - ChildCanExecute 多命令。

API 描述

委托命令有两个委托属性:CanExecuteExecute

//// Delegate properties for commands without parameters
/// <summary>
/// Gets or sets the delegate invoked to execute this command.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Action Execute { get; set; }
/// <summary>
/// Gets or sets the delegate invoked to determine if this command may execute.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Func<bool> CanExecute { get; set; }
//// Delegate properties for commands with parameters
/// <summary>
/// Gets or sets the delegate invoked to execute this command.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Action<T> Execute { get; set; }
/// <summary>
/// Gets or sets the delegate invoked to determine if this command may execute.
/// Setting this does not raise <see cref="CanExecuteChanged"/>.
/// </summary>
public Func<T, bool> CanExecute { get; set; }

复合命令有一个子列表。AutomaticCompositeCommand 类将其公开为一个可枚举属性:public IEnumerable<ICommand> Subcommands,它可以设置为 LINQ 查询的结果。每次调用 CanExecuteExecute 时,都会枚举 Subcommands,重新评估查询。相比之下,CompositeCommand 类需要订阅其子命令的 CanExecuteChanged 事件,因此它充当命令的集合,实现 IList<ICommand>

MultiCommand 类也有一个类似子列表的结构。该类有两个值得注意的属性:

/// <summary>
/// Gets or sets the source collection of this multiproperty.
/// This class is more efficient if the source collection implements
/// <see cref="IList"/> and <see cref="INotifyCollectionChanged"/>.
/// </summary>
public IEnumerable SourceCollection { get; set; } // (not the actual implementation)
/// <summary>
/// Gets or sets the property path applied to the elements in the source collection.
/// </summary>
public string Path { get; set; } // (not the actual implementation)

MultiCommand 将监视 SourceCollection(如果它实现了 INotifyCollectionChanged);这通常设置为 ObservableCollection<T>CollectionViewPath 属性是一个简单的属性路径,例如“ChildVM.SaveCommand”。该路径会针对源集合的每个元素进行评估,从而生成一个子命令集合。此外,MultiCommand 将监视属性路径上的每个对象(如果它实现了 INotifyPropertyChanged)并相应地更新自身。

ManualCommandManualCommand<T> 都支持手动和多订阅通知。手动通知使用 NotifyCanExecuteChanged 方法,并且这些类还公开了一个只读委托属性 CanExecuteChangedSubscription,可用于订阅其他命令的 ICommand.CanExecuteChanged 事件。(有关此必要性的说明,请参阅下面的“关于 ICommand.CanExecuteChanged 的警告”。)请注意,对多订阅命令的此支持不使用弱事件,因此 Nito 命令对象的生存期与提供状态更改通知的所有对象的生存期相关联。

与其他解决方案的比较

已经开发了几种现有的解决方案:

  1. Josh Smith 的 RelayCommand - 最受欢迎的选择之一,来自他 2009 年 2 月的里程碑式 MSDN 文章。这是一个自动委托命令。Nito.MVVM.AutomaticCommand<object>RelayCommand 相比只有细微的 API 差异。
  2. Ocean 的 RelayCommandRelayCommand<T> - 来自 Karl Shifflett 的博客。这是一个自动委托命令,几乎与 Josh Smith 的 RelayCommand 完全相同。
  3. Marlon 的 SimpleCommand,来自他的博客文章。这也是一个自动委托命令,其 API 与 Nito.MVVM.AutomaticCommand<object> 更加相似。
  4. Prism 的 DelegateCommand<T>,来自 2009 年 2 月的版本。这是一个手动委托命令,因此它与 Nito.MVVM.ManualCommand<T> 相似。但是,它存在一个错误,因为它没有将 CanExecuteChanged 实现为弱事件。不过,遇到此错误的情况很少,它只是一个生存期扩展错误。
  5. Prism 的 CompositeCommand,来自同一来源。它与 Nito.MVVM.CompositeCommand 相似(尽管 Prism 的 CompositeCommand 也旨在用作多命令)。但是,Prism 的 CompositeCommand 不仅将 CanExecuteChanged 实现为强事件,而且还假设其子命令将其实现为强事件。这个错误更严重:除非所有子命令都是 Prism 委托命令,否则可能导致事件丢失。
  6. MVVM Toolkit 的 DelegateCommandDelegateCommand<T>,来自 2009 年 5 月的版本。这些是出色的类,满足了静态委托、自动委托和手动委托命令的需求。与 Prism 的命令不同,这些类具有正确的 CanExecuteChanged 实现。
  7. Caliburn 的命令,来自更改集 26106(2009 年 7 月)。Caliburn 将其命令构建在其 Action 系统之上;当它们绑定到元素时,它们才成为 WPF 命令。因此,它们可以被视为一种可以通过 XAML 定义的自动委托命令。Caliburn 也有复合命令的概念。

关于 ICommand.CanExecuteChanged 的警告

对于那些尝试创建自己的 ICommand 派生类型的人来说,有一点警告:ICommand.CanExecuteChanged **必须**实现为弱事件。Nathan Nesbit 在他的博客上对这个问题的描述是我找到的最好的,尽管他从编写自定义控件的角度来解决这个问题,而不是自定义命令

可重用组件

Nito.MVVM 库中的所有源代码都符合 FxCop 和 StyleCop 标准,下载中包含一套单元测试。有许多类型用于支持命令类;其中一些更有趣的类型包括:

  • SimplePropertyPath - 提供了一个非常简单的准绑定。与 System.Windows.Data.Binding 不同,SimplePropertyPath 不需要依赖项属性。它仅将现有的 INotifyPropertyChanged 实现“向前”传播到其他类,并将写入“向后”传播到原始属性。请查看单元测试以获取一些示例。
  • ProjectedCollection - 接受一个源集合和一个简单的属性路径,并沿该路径“投影”源集合的每个元素。MultiCommand 使用 ProjectedCollection 将子 ViewModel 对象序列视为 ICommand 对象集合。任何集合或属性更改都会被检测和传播。
  • WeakCollection<T> - 实现一个弱引用集合,该集合在迭代时会自我清理。CanExecuteChangedCore 使用它来正确实现 ICommand.CanExecuteChanged
  • 许多类协同工作,为 INotifyPropertyChanged 提供“编译时反射”,从而无需使用属性名的文字字符串。值得注意的类包括 NotifyPropertyChangedBase<T>PropertyChangedEventHandlerExtensions。代码中还提供了对解决此问题的其他解决方案的进一步引用。

官方位置

本文附带的代码是 Nito.MVVM 库的预发布版本。官方发布通过 Nito.MVVM CodePlex 主页进行。

谢谢!

欢迎投票和评论!

文章历史

  • 2009-07-21 - 初始修订。
  • 2009-07-24 - 从“与其他解决方案的比较”部分删除了 Onyx 的 RelayCommand,因为它实际上不是 Onyx 框架的一部分,并且将在未来版本中删除。
© . All rights reserved.