Prism for WPF:基础入门





5.00/5 (5投票s)
Prism 框架概述
引言
Prism 是一个框架,能够实现松耦合应用程序的开发,这些应用程序具有灵活性、可维护性和易于测试的特点。Prism 应用程序由模块组成——这些模块是功能单元,松耦合地封装了应用程序的整体功能。在团队协作的环境中,模块可以被单独开发、测试和部署,从而最大限度地减少跨团队依赖,并使团队或团队中的个人能够专注于应用程序的特定方面。Prism 可用于开发遵循 MVVM 设计模式的桌面或移动应用程序,因为它支持创建 WPF、Xamarin 和 UnoPlatform 项目。
背景
本文将通过一个展示虚构员工资料的示例应用程序,对 Prism for WPF 进行基础概述。该示例项目可以从 GitHub 克隆或下载。

示例应用程序包含四个项目:一个 WPF 应用程序项目;一个包含共享代码的类库;以及两个 Prism 模块。

提示:设置 Prism 项目的最简单方法是,首先安装 VS Prism Template Pack 扩展,并利用 Prism 项目模板。示例应用程序中的主项目是使用 Prism Blank App (WPF) 模板创建的,而模块则是使用 Prism Module (WPF) 模板添加的。

Prism
Shell、Regions 和 Views
Prism 应用程序由一个shell组成,该 shell 托管应用程序的所有可视化组件。按照 Prism 的惯例,并且默认情况下,Prism 应用项目模板将 MainWindow
设置为应用程序的 shell。
public partial class App
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
...
}
Shell 包含一个或多个regions,模块可以在其中注入 views,而 view 又可以包含 regions,其他 views 可以在其中放置。下面的图表来自 Prism 文档,重点介绍了这种设置,

要将 view 放置到 region 中,模块会使用 RegionManager
,它会跟踪应用程序中的所有 regions。示例应用程序只有一个名为 ContentRegion
的 region,
<mah:MetroWindow x:Class="StaffStuff.Views.MainWindow"
...
xmlns:prism="http://prismlibrary.com/"
xmlns:common="clr-namespace:StaffStuff.Common;assembly=StaffStuff.Common"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
...
prism:ViewModelLocator.AutoWireViewModel="True"
...>
...
<Grid>
<ContentControl prism:RegionManager.RegionName=
"{x:Static common:RegionNames.ContentRegion}" />
</Grid>
</mah:MetroWindow>
示例应用程序有两个 views:一个显示包含一些员工详细信息的卡片的 user control;另一个 user control 显示特定员工的更多详细信息。后者 user control 还包含一个返回到第一个 view 的按钮。

这两个 views 位于一个名为 UIModule
的模块中,顾名思义,这个模块是 UI 相关的。(示例应用程序中的模块是按“水平层”组织的。如果应用程序是围绕垂直切片组织的,那么每个 view 都会在不同的模块中,并且这些模块将包含特定于 view 的功能。)
UIModule
实现 Prism 的 IModule
接口。该接口有两个方法:一个是在模块初始化时调用的方法,另一个用于类型注册(创建项目时,我将 Unity 设置为 IoC 容器。Prism 项目向导为您提供了使用 Unity 或 DryIoc 的选项)。
using StaffStuff.UI.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using StaffStuff.Common;
namespace StaffStuff.UI
{
public class UIModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RequestNavigate(RegionNames.ContentRegion, nameof(StaffView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<StaffView>();
containerRegistry.RegisterForNavigation<StaffDetailsView>();
}
}
}
在 OnInitialized()
中,我请求 RegionManager
将 StaffView
放置在 ContentRegion
中。因此,StaffView
将是应用程序启动时显示的默认 view。在 RegisterTypes()
中,我指定两个 views 都可用于导航。这使得可以使用 Prism 的导航功能在两个 views 之间来回移动。
View Models 和导航
每个 views 的 DataContext 都设置为一个 view model,该 view model 使用 Prism 的 ViewModelLocator
与 view 相关联。Prism 使用默认约定将 view model 与 view 相关联,该约定假定 view model 与 view 在同一个程序集中;位于 .ViewModels
子命名空间下;名称与 view 的名称对应;并且相应的 view 位于 .Views
子命名空间下。(如果您更喜欢使用不同的约定,可以指定一个 自定义 的。)UI 模块有两个 view models,每个都与一个特定的 view 相关联。

View models 继承一个基类,该基类继承 Prism 的 BindableBase
并实现其 INavigationAware
接口。
using Prism.Mvvm;
using Prism.Regions;
namespace StaffStuff.UI.ViewModels
{
public class ViewModelBase : BindableBase, INavigationAware
{
protected IRegionManager RegionManager { get; }
public ViewModelBase(IRegionManager regionManager)
{
RegionManager = regionManager;
}
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public virtual void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public virtual void OnNavigatedTo(NavigationContext navigationContext)
{
}
}
}
BindableBase
只是 Prism 对 INotifyPropertyChanged
的实现,而 INavigationAware
使 view model 能够参与导航过程。
StaffViewModel
包含一个 DelegateCommand
,其执行方法指示 RegionManager
导航到员工详细信息 view——DelegateCommand
是 Prism 对 ICommand
接口的实现。
using System.Collections.Generic;
using Prism.Commands;
using Prism.Regions;
using StaffStuff.Common;
using StaffStuff.Common.Interfaces;
using StaffStuff.Common.Models;
namespace StaffStuff.UI.ViewModels
{
public class StaffViewModel : ViewModelBase
{
private List<Employee> _employees;
public List<Employee> Employees
{
get => _employees;
set => SetProperty(ref _employees, value);
}
public DelegateCommand<Employee> EmployeeDetailsCommand { get; }
public StaffViewModel(IStaffData staffData, IRegionManager regionManager) :
base(regionManager)
{
Employees = staffData.GetEmployees();
EmployeeDetailsCommand = new DelegateCommand<Employee>(StaffDetails);
}
private void StaffDetails(Employee employee)
{
var parameters = new NavigationParameters();
parameters.Add(nameof(Employee), employee);
RegionManager.RequestNavigate
(RegionNames.ContentRegion, "StaffDetailsView", parameters);
}
}
}
在导航请求中会传递一个参数。此参数是一个 Employee
对象,它被添加到 NavigationParameters
集合中,并带有一个唯一标识它的键。导航到的 view 的 view model 可以使用该键来检索特定的对象。
using Prism.Commands;
using Prism.Regions;
using StaffStuff.Common.Models;
namespace StaffStuff.UI.ViewModels
{
public class StaffDetailsViewModel : ViewModelBase
{
private Employee _employee;
public Employee Employee
{
get => _employee;
set => SetProperty(ref _employee, value);
}
private IRegionNavigationJournal _journal;
public DelegateCommand GoBackCommand { get; }
public StaffDetailsViewModel(IRegionManager regionManager) : base(regionManager)
{
GoBackCommand = new DelegateCommand(GoBack);
}
private void GoBack() => _journal.GoBack();
public override void OnNavigatedTo(NavigationContext navigationContext)
{
Employee = navigationContext.Parameters[nameof(Employee)] as Employee;
_journal = navigationContext.NavigationService.Journal;
}
}
}
使用导航日志(navigation journal)进行返回到上一个 view 的导航。导航日志还有一个 GoForward()
方法,可用于向前导航。
模块注册
示例应用程序中的模块必须注册到 Prism 的 ModuleCatalog
中,以便应用程序加载它们。这在 App.xaml.cs 中完成。
public partial class App
{
...
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ServicesModule>();
moduleCatalog.AddModule<UIModule>();
}
}
结论
我已经介绍了一些 Prism for WPF 的方面,但还有几个我没有重点介绍的领域;例如不同的导航策略、Prism 的事件聚合器(event aggregator)和复合命令(composite commands)。本文希望能为您提供一个基础平台,让您可以深入了解 Prism 的其他方面。我建议您查看 GitHub 上各种 Prism 示例 项目,并查阅 文档 以获得更广泛的视野。
历史
- 2020 年 7 月 1 日:初次发布