[Windows]Prism 8.0入门[上]
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 主要提供了下面三方面的功能:
BindableBase
和ErrorsContainer
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.SetErrors
和 ErrorsContainer.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 会根据 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);
}
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)
除了基本的调用方式,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 的更多功能将在下一篇文章中介绍。
Subscribe to my newsletter
Read articles from Edward Chu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Edward Chu
Edward Chu
Science is gold.