[Windows]Prism 8.0入门[上]

shineyshiney
4 min read

Prism介绍

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

Prism.Core、Prism.Wpf 和 Prism.Unity

WPF 平台的项目已经大幅删减,只保留了 Prism.Wpf、Prism.DryIoc 和 Prism.Unity,也就是说现在 Prism 只支持 DryIoc 和 Unity 两种 IOC 容器。这样一来 Prism 项目的结构就很清晰了。

以 WPF 为例,

核心的项目是 Prism.Core,它提供实现 MVVM 模式的核心功能以及部分各平台公用的类。然后是 Prism.Wpf,它提供针对 Wpf 平台的功能,包括导航、弹框等。

最后由 Prism.Unity 指定 Unity 作为 IOC 容器。

Prism.Core

Prism.Core 可以单独安装:

Install-Package Prism.Core -Version 8.0.0.1909

作为一个 MVVM 库 Prism.Core 主要提供了下面三方面的功能:

  • BindableBaseErrorsContainer

  • Commanding

  • Event Aggregator

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

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
    {
        get
        {
            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.SetErrorsErrorsContainer.ClearErrors 管理数据验证的错误信息

public class MockValidatingViewModel : DomainObject
{
    private int mockProperty;

    public int MockProperty
    {
        get
        {
            return mockProperty;
        }

        set
        {
            SetProperty(ref mockProperty, value);

            if (mockProperty < 0)
                ErrorsContainer.SetErrors(() => MockProperty, new string[] { "value cannot be less than 0" });
            else
                ErrorsContainer.ClearErrors(() => MockProperty);
        }
    }
}

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 会根据 ICommandCanExecute 函数的返回值来判断触发此 Command 的 UI 元素是否可用。CanExecute 返回 DelegateCommand 构造函数中的第二个参数 canExecuteMethod 的返回值。如果不传入这个参数,则 CanExecute 一直返回 True。

如果 CanExecute 的返回值有变化,可以调用 RaiseCanExecuteChanged 函数,它会触发 CanExecuteChanged 事件并通知 UI 元素重新判断绑定的 ICommand 是否可用。除了主动调用 RaiseCanExecuteChangedDelegateCommand 还可以用 ObservesPropertyObservesCanExecute 两种形式监视属性,定于属性的 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);
}

Event Aggregator

本来Event Aggregator(事件聚合器)或 Messenger 之类的组件本来并不是 MVVM 的一部分,不过现在也成了 MVVM 框架的一个重要元素。

解耦是 MVVM 的一个重要目标,'EventAggregator' 则是实现解耦的重要工具。在 MVVM 中,对于 View 和与他匹配的 ViewModel 之间的交互,可以使用 INotifyPropertyIcommand

而对于必须通信的不同 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)

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

Copypublic 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
    }
}

生产力工具

如果觉得属性和 DelegateCommand 的定义有些啰嗦,可以试试安装这个工具:

Prism Template Pack,它提供了一些实用的代码段和一些 Project 和 Item 的模板。

例如,安装此工具后可以通过 cmd 代码段快速生成一个完整的 DelegateCommand 代码:

private DelegateCommand _fieldName;
public DelegateCommand CommandName =>
    _fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName));

void ExecuteCommandName()
{
}

Prism 的更多功能将在下一篇文章中介绍。

0
Subscribe to my newsletter

Read articles from shiney directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

shiney
shiney