添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
[Windows] Prism 8.0 入门(上):Prism.Core

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

1. Prism 简介

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

Prism 是一个有10年以上历史的框架,而上个月才刚发布了它的 8.0 版本 ,这意味着现在网上能找到的大部分 Prism 的资料都已经有点过时,连 官方文档 也不例外。如果你需要详细的文档,除了官方文档,我会推荐 RyzenAdorer 的 Prism 系列文章:

NET Core 3 WPF MVVM框架 Prism系列文章索引 - RyzenAdorer -

如果你不需要那么详细的文档,只需要一个入门的教程,那么我希望我写的这两篇文章可以帮到你。

2. Prism.Core、Prism.Wpf 和 Prism.Unity

从很久以前开始, 臃肿 就是 Prism 被提起最多的标签。毕竟比起 MVVMLight,Prism 实现的功能更多;对于初学者来说,刚打开 Prism 的文档很可能会马上选择放弃。Prism 的文档详细到让人望而却步,例如多年前的旧版官方文档的 其中一篇



不是 6 分钟,不是 16 分账,是整整 60 分钟,Prism 的旧文档随便打开一篇都吓死人。而 Prism 的各种包更是多到离谱。例如几年前的 Prism 6.3,其中 WPF 平台的项目有这么多个:

  • Prism.Wpf
  • Prism.Autofac
  • Prism.DryIoc
  • Prism.Mef
  • Prism.Ninject
  • Prism.StructureMap
  • Prism.Unity

所以臃肿是很多人对 Prism 的印象。

减肥是一个永恒的受欢迎的话题,对 Prism 也是一样。相比 Prism 6.3,刚刚发布的 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 容器。



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

3. Prism.Core

Prism.Core 可以单独安装,目前最新的版本是 8.0.0.1909:

Install-Package Prism.Core -Version 8.0.0.1909

除了一些各个平台都用到的零零碎碎的公用类,作为一个 MVVM 库 Prism.Core 主要提供了下面三方面的功能:

  • BindableBase 和 ErrorsContainer
  • Commanding
  • Event Aggregator

这些功能已经覆盖了 MVVM 的核心功能,如果只需要与具体平台无关的 MVVM 功能,可以只安装 Prism.Core。

4. BindableBase 和 ErrorsContainer

数据绑定是 MVVM 的核心元素之一,为了使绑定的数据可以和 UI 交互,数据类型必须继承 INotifyPropertyChanged BindableBase 实现了 INotifyPropertyChanged 最简单的封装,它的使用如下:

public class MockViewModel : BindableBase
    private string _myProperty;
    public string MyProperty
        get { return _myProperty; }
        set { SetProperty(ref _myProperty, value); }
}

其中 SetProperty 判断 _myProperty 和 value 是否相等,如果不相等就为 _myProperty 赋值并触发 OnPropertyChanged 事件。

除了 INotifyPropertyChanged ,绑定机制中另一个十分有用的接口是 INotifyDataErrorInfo ,它用于公开数据验证的结果。Prism 提供了 ErrorsContainer 以便管理及通知数据验证的错误信息。要使用 ErrorsContainer ,可以先写一个类似这样的基类:

public class DomainObject : BindableBase, INotifyDataErrorInfo
    public ErrorsContainer<string> _errorsContainer;
    protected ErrorsContainer<string> ErrorsContainer
            if (_errorsContainer == null)
                _errorsContainer = new ErrorsContainer<string>(s => OnErrorsChanged(s));
            return _errorsContainer;
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    public void OnErrorsChanged(string propertyName)
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    public IEnumerable GetErrors(string propertyName)
        return ErrorsContainer.GetErrors(propertyName);
    public bool HasErrors
        get { return ErrorsContainer.HasErrors; }
}

然后就可以在派生类中通过 ErrorsContainer.SetErrors ErrorsContainer.ClearErrors 管理数据验证的错误信息:

public class MockValidatingViewModel : DomainObject
    private int mockProperty;
    public int MockProperty
            return mockProperty;
            SetProperty(ref mockProperty, value);
            if (mockProperty < 0)
                ErrorsContainer.SetErrors(() => MockProperty, new string[] { "value cannot be less than 0" });
                ErrorsContainer.ClearErrors(() => MockProperty);
}

5. Commanding

ICommand 同样是 MVVM 模式的核心元素, DelegateCommand 实现了 ICommand 接口,它最基本的使用形式如下,其中 DelegateCommand 构造函数中的第二个参数 canExecuteMethod 是可选的:

public DelegateCommand SubmitCommand { get; private set; }
public CheckUserViewModel()
    SubmitCommand = new DelegateCommand(Submit, CanSubmit);
private void Submit()
    //implement logic
private bool CanSubmit()
    return true;
}

另外它还有泛型的版本:

public DelegateCommand<string> SubmitCommand { get; private set; }
public CheckUserViewModel()
    SubmitCommand = new DelegateCommand<string>(Submit, CanSubmit);
private void Submit(string parameter)
    //implement logic
private bool CanSubmit(string parameter)
    return true;
}

通常 UI 会根据 ICommand CanExecute 函数的返回值来判断触发此 Command 的 UI 元素是否可用。 CanExecute 返回 DelegateCommand 构造函数中的第二个参数 canExecuteMethod 的返回值。如果不传入这个参数,则 CanExecute 一直返回 True。

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

private bool _isEnabled;
public bool IsEnabled
    get { return _isEnabled; }
    set { SetProperty(ref _isEnabled, value); }
private bool _canSave;
public bool CanSave
    get { return _canSave; }
    set { SetProperty(ref _canSave, value); }
public CheckUserViewModel()
    SubmitCommand = new DelegateCommand(Submit, CanSubmit).ObservesProperty(() => IsEnabled);
    //也可以写成串联方式
    SubmitCommand = new DelegateCommand(Submit, CanSubmit).ObservesProperty(() => IsEnabled).ObservesProperty<bool>(() => CanSave);
    SubmitCommand = new DelegateCommand(Submit).ObservesCanExecute(() => IsEnabled);
}

6. Event Aggregator

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



要使用 EventAggregator ,首先需要定义 PubSubEvent

public class TickerSymbolSelectedEvent : PubSubEvent<string>{}

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

public class MainPageViewModel
    IEventAggregator _eventAggregator;
    public MainPageViewModel(IEventAggregator ea)
        _eventAggregator = ea;
}

发送方的操作很简单,只需要 通过 GetEvent 拿到 PubSubEvent ,把消息发布出去,然后拍拍屁股走人,其它的责任都不用管:

_eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish("STOCK0");

订阅方是真正使用这些消息并负责任的人,下面是最简单的通过 Subscribe 订阅事件的代码:

public class MainPageViewModel
    public MainPageViewModel(IEventAggregator ea)
        ea.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews);
    void ShowNews(string companySymbol)
        //implement logic
}

除了基本的调用方式, Subscribe 函数还有其它可选的参数:

public virtual SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)

其中 threadOption 指示收到消息后在哪个线程上执行第一个参数定义的 action ,它有三个选项:

  • PublisherThread,和发布者保持在同一个线程上执行。
  • UIThread,在 UI 线程上执行。
  • BackgroundThread,在后台线程上执行。

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

  • 设置为 false 时,引用为弱引用,用完可以不用管。
  • 设置为 true 时,引用为强引用,用完需要使用 Unsubscribe 取消订阅。

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

public class MainPageViewModel
    TickerSymbolSelectedEvent _event;
    public MainPageViewModel(IEventAggregator ea)
        _event = ea.GetEvent<TickerSymbolSelectedEvent>();
        _event.Subscribe(ShowNews);
    void Unsubscribe()
        _event.Unsubscribe(ShowNews);
    void ShowNews(string companySymbol)
        //implement logic
}

7. 生产力工具

如果觉得属性和 DelegateCommand 的定义有些啰嗦,可以试试安装这个工具: Prism Template Pack ,它提供了一些实用的代码段和一些 Project 和 Item 的模板。例如,安装此工具后可以通过 cmd 代码段快速生成一个完整的 DelegateCommand 代码:

private DelegateCommand _fieldName;
public DelegateCommand CommandName =>