Prism简介

Prism 是一个用于构建松耦合、可维护和可测试的 XAML 应用的框架,它支持所有还活着的基于 XAML 的平台,包括 WPF、Xamarin Forms、WinUI 和 Uwp Uno。Prism 提供了一组设计模式的实现,这些模式有助于编写结构良好且可维护的 XAML 应用程序,包括 MVVM、依赖项注入、命令、事件聚合器等。

Prism.Core、Prism.Wpf 和 Prism.Unity

相比于MVVMLight(在这个里面才接触过这个框架),Prism实现的功能更多,所以就显得比较臃肿。刚发布的8.0版本已经好了很多,例如 WPF 平台的项目已经大幅删减,只保留了 Prism.Wpf、Prism.DryIoc 和 Prism.Unity,也就是说现在 Prism 只支持 DryIoc 和 Unity 两种 IOC 容器。这样一来 Prism 项目的结构就很清晰了。

以 WPF 为例,核心的项目是 Prism.Core,它提供实现 MVVM 模式的核心功能以及部分各平台公用的类。然后是 Prism.Wpf,它提供针对 Wpf 平台的功能,包括导航、弹框等。最后由 Prism.Unity 指定 Unity 作为 IOC 容器。

[Windows] Prism 8.0 入门(上):Prism.Core - 图1

即使已精简了这么多,Prism 还是有很多功能,两篇文章也不足以讲解全部内容,所以我只会介绍最常用到的入门知识。这篇文章首先介绍 Prism.Core 的主要功能。

Prism.Core

除了一些各个平台都用到的零零碎碎的公用类,作为一个 MVVM 库 Prism.Core 主要提供了下面三方面的功能:
  1. - <font style="color:rgb(49, 70, 89);">BindableBase ErrorsContainer</font>
  2. - <font style="color:rgb(49, 70, 89);">Commanding</font>
  3. - <font style="color:rgb(49, 70, 89);">Event Aggregator</font>
这些功能已经覆盖了 MVVM 的核心功能,如果只需要与具体平台无关的 MVVM 功能,可以只安装 Prism.Core。

BindableBase 和 ErrorsContainer

数据绑定是 MVVM 的核心元素之一,为了使绑定的数据可以和 UI 交互,数据类型必须继承 INotifyPropertyChanged<font style="color:rgb(49, 70, 89);background-color:rgb(242, 244, 245) !important;">BindableBase</font>实现了 INotifyPropertyChanged 最简单的封装,它的使用如下:
  1. public class MockViewModel : BindableBase
  2. {
  3. private string _myProperty;
  4. public string MyProperty
  5. {
  6. get { return _myProperty; }
  7. set { SetProperty(ref _myProperty, value); }
  8. }
  9. }
  1. 其中,`SetProperty`判断_myProperty value 是否相等,如果不相等就为 _myProperty 赋值并触发`OnPropertyChanged`事件。

除了INotifyPropertyChanged,绑定机制中另一个十分有用的接口是 INotifyDataErrorInfo,它用于公开数据验证的结果。Prism 提供了<font style="color:rgb(49, 70, 89);background-color:rgb(242, 244, 245) !important;">ErrorsContainer</font>以便管理及通知数据验证的错误信息。要使用<font style="color:rgb(49, 70, 89);background-color:rgb(242, 244, 245) !important;">ErrorsContainer</font>,可以先写一个类似这样的基类:

  1. public class DomainObject : BindableBase, INotifyDataErrorInfo
  2. {
  3. public ErrorsContainer<string> _errorsContainer;
  4. protected ErrorsContainer<string> ErrorsContainer
  5. {
  6. get
  7. {
  8. if (_errorsContainer == null)
  9. _errorsContainer = new ErrorsContainer<string>(s => OnErrorsChanged(s));
  10. return _errorsContainer;
  11. }
  12. }
  13. public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
  14. public void OnErrorsChanged(string propertyName)
  15. {
  16. ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
  17. }
  18. public IEnumerable GetErrors(string propertyName)
  19. {
  20. return ErrorsContainer.GetErrors(propertyName);
  21. }
  22. public bool HasErrors
  23. {
  24. get { return ErrorsContainer.HasErrors; }
  25. }
  26. }
  1. 然后就可以在派生类中通过`ErrorsContainer.SetErrors``ErrorsContainer.ClearErrors`管理数据验证的错误信息:
  1. public class MockValidatingViewModel : DomainObject
  2. {
  3. private int mockProperty;
  4. public int MockProperty
  5. {
  6. get
  7. {
  8. return mockProperty;
  9. }
  10. set
  11. {
  12. SetProperty(ref mockProperty, value);
  13. if (mockProperty < 0)
  14. ErrorsContainer.SetErrors(() => MockProperty, new string[] { "value cannot be less than 0" });
  15. else
  16. ErrorsContainer.ClearErrors(() => MockProperty);
  17. }
  18. }
  19. }

Commanding

ICommand 同样是 MVVM 模式的核心元素,<font style="color:rgb(37, 37, 37);">DelegateCommand</font>实现了<font style="color:rgb(37, 37, 37);">ICommand</font>接口,它最基本的使用形式如下,其中<font style="color:rgb(37, 37, 37);">DelegateCommand</font>构造函数中的第二个参数<font style="color:rgb(37, 37, 37);">canExecuteMethod</font>是可选的:

  1. public DelegateCommand SubmitCommand { get; private set; }
  2. public CheckUserViewModel()
  3. {
  4. SubmitCommand = new DelegateCommand(Submit, CanSubmit);
  5. }
  6. private void Submit()
  7. {
  8. //implement logic
  9. }
  10. private bool CanSubmit()
  11. {
  12. return true;
  13. }
  1. 另外它还有泛型的版本:
  1. public DelegateCommand<string> SubmitCommand { get; private set; }
  2. public CheckUserViewModel()
  3. {
  4. SubmitCommand = new DelegateCommand<string>(Submit, CanSubmit);
  5. }
  6. private void Submit(string parameter)
  7. {
  8. //implement logic
  9. }
  10. private bool CanSubmit(string parameter)
  11. {
  12. return true;
  13. }
  1. 通常 UI 会通过`ICommand``CanExecute`函数的返回值来判断触发此 Command UI 元素是否可用。`CanExecute`返回`DelegateCommand`构造函数中的第二个参数`canExecuteMethod`的返回值。如果不传入这个参数,则`CanExecute`一直返回 True

如果CanExecute的返回值有变化,可以调用RaiseCanExecuteChanged函数,它会触发CanExecuteChanged事件并通知 UI 元素重新判断绑定的ICommand是否可用。除了主动调用RaiseCanExecuteChangedDelegateCommand还可以用ObservesPropertyObservesCanExecute两种形式监视属性,定义属性的PropertyChanged事件并改变CanExecute

  1. private bool _isEnabled;
  2. public bool IsEnabled
  3. {
  4. get { return _isEnabled; }
  5. set { SetProperty(ref _isEnabled, value); }
  6. }
  7. private bool _canSave;
  8. public bool CanSave
  9. {
  10. get { return _canSave; }
  11. set { SetProperty(ref _canSave, value); }
  12. }
  13. public CheckUserViewModel()
  14. {
  15. SubmitCommand = new DelegateCommand(Submit, CanSubmit).ObservesProperty(() => IsEnabled);
  16. //也可以写成串联方式
  17. SubmitCommand = new DelegateCommand(Submit, CanSubmit).ObservesProperty(() => IsEnabled).ObservesProperty<bool>(() => CanSave);
  18. SubmitCommand = new DelegateCommand(Submit).ObservesCanExecute(() => IsEnabled);
  19. }

Event Aggregator

本来Event Aggregator(事件聚合器)或Message之类的组件本来并不是MVVM的一部分,不过现在也成了MVVM框架的一个重要元素。解耦是MVVM的一个重要目标,’EventAggregator’则是实现解耦的重要工具。在MVVM中,对于View和与它匹配的ViewModel之间的交互,可以使用INotifyPropertyICommand;而对于必须通信的不同ViewModel或模块,为了使他们之间实现低耦合,可以使用Prism中的EventAggregator。如下图所示, Publisher和Scbscriber之间没有直接关联,它们通过 Event Aggregator获取PubSubEvent并发送及接收消息:

[Windows] Prism 8.0 入门(上):Prism.Core - 图2

要使用EventAggregator,首先要定义PubSubEvent

<font style="color:rgb(167, 29, 93);">publicclass</font><font style="color:rgb(121, 93, 163);">TickerSymbolSelectedEvent</font><font style="color:rgb(51, 51, 51);background-color:rgb(242, 244, 245);"> : </font><font style="color:rgb(121, 93, 163);">PubSubEvent</font><font style="color:rgb(51, 51, 51);background-color:rgb(242, 244, 245);"><</font><font style="color:rgb(121, 93, 163);">string</font><font style="color:rgb(51, 51, 51);background-color:rgb(242, 244, 245);">>{}</font>

发布方和订阅方都通过EventAggregator索取PubSubEvent,在ViewModel中通常是通过依赖注入获取一个IEventAggregator

  1. public class MainPageViewModel
  2. {
  3. IEventAggregator _eventAggregator;
  4. public MainPageViewModel(IEventAggregator ea)
  5. {
  6. _eventAggregator = ea;
  7. }
  8. }
  1. 发送方的操作很简单,只需要通过`GetEvent`拿到`PubSubEvent`,把消息发布出去就可以了:

<font style="color:rgb(51, 51, 51);background-color:rgb(242, 244, 245);">_eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish(</font><font style="color:rgb(223, 80, 0);">"STOCK0"</font><font style="color:rgb(51, 51, 51);background-color:rgb(242, 244, 245);">);</font>

  1. 订阅方是真正使用这些消息并负责任的人,下面是最简单的通过`Subscribe`订阅事件的代码:
  1. public class MainPageViewModel
  2. {
  3. public MainPageViewModel(IEventAggregator ea)
  4. {
  5. ea.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews);
  6. }
  7. void ShowNews(string companySymbol)
  8. {
  9. //implement logic
  10. }
  11. }
  1. 除了最基本的方式,`Subscribe`函数还有其他可选的参数:
  1. public virtual SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
  1. 其中`threadOption`指示收到消息后在哪个线程上执行第一个参数定义的`action`,它有三个选项:
  2. - PublisherThread,和发布者在同一个线程上执行
  3. - UIThread,在UI线程上执行
  4. - BackgroundThread,在后台线程上执行

第三个参数keepSubscriberReferenceAlive默认为 false,它指示该订阅是否为强引用。

  1. - 设置为false时,引用为弱引用,用完可以不用管。
  2. - 设置为true时,引用为强应用,用完需要使用`Unsubscribe`取消订阅。

下面代码是一段订阅及取消订阅的示例:

  1. public class MainPageViewModel
  2. {
  3. TickerSymbolSelectedEvent _event;
  4. public MainPageViewModel(IEventAggregator ea)
  5. {
  6. _event = ea.GetEvent<TickerSymbolSelectedEvent>();
  7. _event.Subscribe(ShowNews);
  8. }
  9. void Unsubscribe()
  10. {
  11. _event.Unsubscribe(ShowNews);
  12. }
  13. void ShowNews(string companySymbol)
  14. {
  15. //implement logic
  16. }
  17. }

生产力工具

如果觉得属性和DelegateCommand的定义有些啰嗦,可以安装工具:Prism Template Pack,它提供了一些实用的代码段和一些 Project 和 Item 的模板。例如,安装此工具后可以通过<font style="color:rgb(37, 37, 37);">cmd</font>代码段快速生成一个完整的<font style="color:rgb(37, 37, 37);">DelegateCommand</font>代码:

  1. private DelegateCommand _fieldName;
  2. public DelegateCommand CommandName =>
  3. _fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName));
  4. void ExecuteCommandName()
  5. {
  6. }
  1. 更多代码段定义请参考官方文档:[Productivity Tools Prism](https://prismlibrary.com/docs/getting-started/productivity-tools.html)

结语

Prism.Core 最初由 Microsoft Patterns and Practices 团队创建,现在转移到社区。虽然 Prism 框架非常成熟(还有点臃肿),支持插件和定位控件的区域,但 Prism.Core 很轻,仅包含几个常用的类型。这篇文章已经把 Prism.Core 中最常用的类尽可能简单地介绍过一遍,这足够用完创建一个基于 MVVM 框架的项目。 Prism 的更多功能将在下一篇文章中介绍。

参考

https://github.com/PrismLibrary/Prism

https://prismlibrary.com/docs/index.html