外观
WPF Prism Rengion详解
在 WPF Prism 框架中,Region 是用于构建模块化、松耦合 UI 的核心概念。
本质上是一个“占位符”或“容器”,允许在运行时动态注入或替换视图(View,通常是 UserControl、DataTemplate 或自定义控件),从而实现 UI 的组合式开发。
适合大型应用,其中不同模块可以独立开发并动态组装到主界面(Shell)中。Prism 通过 RegionManager 类来管理和维护 Region 的集合
基本原理和工作机制
定义
Region通常附加到 WPF 的 UI 控件上,如ContentControl、ItemsControl、TabControl等。这些控件充当 Region的“宿主” 比如:
<Window x:Class="ShellWindow" xmlns:prism="http://prismlibrary.com/">
<Grid>
<ContentControl prism:RegionManager.RegionName="MainRegion" />
<!--或者-->
<TabControl prism:RegionManager.RegionName="MainRegion"/>
</Grid>
</Window>
<!-- “MainRegion” 就是一个名为MainRegion的Region,附加到ContentControl或TabControl上 -->适配器
Prism 提供了内置适配器来处理不同类型的控件
- ContentControlRegionAdapter: 适用于单内容控件(ContentControl),Region 中只能有一个活跃视图。
- ItemsControlRegionAdapter: 适用于列表控件(如 ItemsControl),可以容纳多个视图
- SelectorRegionAdapter: 适用于选择控件(如 TabControl),支持多个视图并允许切换
注意
初始化函数中,大概率无法找到Region
视图加载策略
视图可以以两种方式加载到 Region 中
- 视图发现(View Discovery): 自动模式,在Region创建时自动查找并注入已注册的视图。适合静态UI布局。
- 视图注入(View Injection): 手动模式,通过代码显式添加视图。适合动态场景,如用户交互触发视图切换。 此外,Region 支持行为扩展(如 AutoPopulateRegionBehavior 用于自动填充,RegionActiveAwareBehavior 用于激活通知),以及 RegionContext 用于在视图间共享数据。
生命周期和管理
RegionManager维护所有 Region 的集合。 可以通过IRegionManager.Regions["RegionName"]访问指定 Region- 添加视图:
region.Add(viewInstance)或region.Add(viewInstance, "ViewName") - 激活/停用视图:
region.Activate(view)或region.Deactivate(view) - 移除视图:
region.Remove(view) - Region还支持作用域(Scoped Regions),允许在子视图中创建独立的 Region 实例,避免全局冲突。
运用场景
- 模块化开发:在企业级 WPF 应用中,主 Shell 定义多个 Region(如 ToolbarRegion、ContentRegion、NavigationRegion),每个模块负责注入自己的视图。
- 导航支持:Region 可以与 Prism 的导航 API 结合,使用 RequestNavigate 方法动态切换视图,支持前进/后退历史(通过 IRegionNavigationJournal)。
- 数据共享:使用 RegionContext 附加属性传递数据,例如:
RegionManager.SetRegionContext(control, dataObject) - 注意事项:Region 只在视图加载到视觉树(Visual Tree)时才创建(延迟创建),以优化性能。不同 Region 类型对视图数量有限制(如 ContentControl 只支持一个活跃视图)
函数详解
RegisterViewWithRegion
RegionManager的扩展函数- 用于自动模式(View Discovery)
- 在模块初始化时调用(IModule的Initialize()),将视图类型与Region关联,当Region被创建的并显示时,prism会自动实例该视图并注入到Region中
- 当名为 regionName 的 Region 第一次被创建时,自动向其中注入一个 TView 实例。”
public class MyModule : IModule
{
private readonly IRegionManager _regionManager;
private readonly IUnityContainer _container;
public void Initialize()
{
// 直接注册视图类型(Prism 会通过容器解析实例)
_regionManager.RegisterViewWithRegion("MainRegion", typeof(MyView));
//或使用委托延迟解析(推荐,用于依赖注入)
_regionManager.RegisterViewWithRegion("MainRegion", () => _container.Resolve<MyView>());
}
}特点
- 自动加载
- 适合单一示例,静态试图(工具栏或侧边栏)
- 试图必须通过容器解析
- 如果Region时ItemsControl类型,可以注册等多个视图,ContentControl就只加载第一个。
关键点:
- 它不是“立即添加”,而是 注册一个“创建回调”
- 每次 Region 创建,Prism 都会检查是否有针对该 Region 的注册 → 全部执行一遍
- 即使 Region 销毁了,注册记录依然存在于 RegionManager 中
- 你重复调用 RegisterViewWithRegion,Prism 不会去重 → 注册多个相同的回调
RegisterForNavigation
IContainerRegistry的扩展函数- 用于手动模式(View Injection),结合prism的Navigation API使用
- 一般在App.xaml.cs的
RegisterTypes或者模块的RegisterTypes调用
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterTypeForNavigation<MyView>(); // 默认使用类型名作为导航键
// 或指定自定义键
containerRegistry.RegisterTypeForNavigation<MyView>("MyViewKey");
}
// 然后在代码中触发导航
private readonly IRegionManager _regionManager;
_regionManager.RequestNavigate("MainRegion", "MyView"); // 或 new Uri("MyView?param=1", UriKind.Relative)特点
- 支持动态导航。通过
RequestNavigate函数注入视图,支持传参 - 视图需要实现
INavigationAware来处理导航事件 - 适合多实例的场景,比如点击按钮切换视图
- 如果视图已存在Region中,导航会激活他,否者创建新示例
RegisterViewWithRegion的RegisterForNavigation区别
| 维度 | RegisterViewWithRegion | RegisterForNavigation (即 RegisterTypeForNavigation) |
|---|---|---|
| 核心模式 | 属于 View Discovery(自动发现),Prism 负责自动注入视图。强调“声明式”注册,视图在 Region 初始化时就加载。 | 属于 View Injection(注入),强调“命令式”导航。通过显式调用 RequestNavigate 来控制视图的加载和切换。 |
| 加载时机和控制 | 自动、被动加载。当 Region 进入视觉树时触发,无需额外代码。适合固定 UI 部分,无法轻松传递参数或处理复杂逻辑。 | 手动、主动加载。通过事件或命令触发导航,支持参数(如 URI 查询字符串)和生命周期事件(INavigationAware)。更灵活,但需要更多代码。 |
| 适用场景 | 静态布局,如始终显示的侧边栏或工具栏。视图通常单例,不支持多次实例化。 | 动态交互,如表单导航、wizard 流程或 tab 切换。支持多个视图实例(每个带不同数据),并集成导航日志(前进/后退)。 |
| 视图管理 | 视图自动添加到 Region 的 Views 集合中。如果是 ContentControl 类型,添加多个会覆盖;ItemsControl 支持多视图。 | 导航时会检查 Region 是否已有视图,如果有则激活,否则注入新视图。支持移除旧视图以避免内存泄漏。 |
| 依赖关系 | 两者都依赖 IoC 容器解析视图实例。但 RegisterViewWithRegion 更简单,直接与 Region 绑定。 | 更紧密集成 Prism 的导航框架(引入于 Prism 5.0+),支持导航历史和确认对话。 |
一句话总结
“固定不动用 RegisterViewWithRegion,想跳来跳去用 RegisterForNavigation。”
RequestNavigate
是导航系统的核心函数,用于向指定的Region发送导航请求,实现视图的动态切换和加载。他是实现View Injection(注入)的关键API,允许通过URI或者字符串出发视图的动态注入激活和替换。支持传参 导航历史。
初始化时机:
- 需要目标 Region(如 ContentControl)已经存在于 IRegionManager.Regions 集合中。
- Region 只有在宿主控件(如 ContentControl)被加载到 视觉树(Visual Tree) 后才会由 Prism 的 RegionManager 创建并注册。
- Region 的创建和注册是延迟初始化的 所以最好不要在构造函数中使用,可以在IModule的OnInitialized中使用(显示初始页面)
在IRegionManager接口中,重载形式:
// 1. 基本形式:导航到指定 URI
public void RequestNavigate(string regionName, Uri source);
// 2. 带回调:支持导航完成后的异步处理
public void RequestNavigate(string regionName, Uri source, Action<NavigationResult> navigationCallback);
// 3. 带参数:使用 QueryString 传递数据
public void RequestNavigate(string regionName, Uri source, NavigationParameters navigationParameters);
// 4. 完整形式:结合回调和参数
public void RequestNavigate(string regionName, Uri source, Action<NavigationResult> navigationCallback, NavigationParameters navigationParameters);
/*
- egionName目标 Region 的名称(如 "MainRegion")。
- source导航目标,通常是 URI(如 new Uri("MyView", UriKind.Relative))或简单字符串(如 "MyView")。这对应于通过 RegisterTypeForNavigation<T> 注册的视图类型名。
- navigationCallback可选回调,接收 NavigationResult 对象,允许处理导航成功/失败(result.Result 为 true/false)。
- navigationParameters:可选参数集合,用于传递数据到目标视图。
*/工作原理与执行流程
执行流程:
解析导航源 Prism解析source uri,提取目标视图名和参数
查找注册视图 在容器中查找与目标名关联的视图模型
需预先通过 RegisterTypeForNavigation 注册的,与上面的函数呼应
检查Region状态 如果目标视图已经存在Views中,直接激活(Activate(view)),不存在则创建视图实例 添加到Views集合激活
激活导航事件 如果实现了INavigationAware,...
处理结果 执行回调,并更新导航日志,以支持前进后退
相关信息
URI 格式:支持相对 URI,如 "ViewName?param1=value1¶m2=value2"。Prism 会自动解析查询字符串到 NavigationParameters。
- 参数传递:参数可以是简单值、复杂对象或自定义类型。目标视图通过 INavigationContext.Parameters 访问它们。
实现INavigationAware:
public partial class CustomerView : UserControl, INavigationAware
{
public bool IsNavigationTarget(NavigationContext navigationContext)
{
// 用来决定是否重用现有的视图实例,只有在重新进行导航时触发
// 它决定是否重用现有的视图实例,而不是创建新的视图实例
//返回 true 表示当前视图实例可以作为导航目标(重用);返回 false 表示需要创建新的视图实例。 需要注意为false的时候可能会创建很多视图在那里
//false时:Prism 不会重用现有视图,而是创建新实例添加到 Region 中。旧视图不会被移除,导致 Region.Views 集合越来越大,可能引起内存泄漏或性能问题。
var customerId = navigationContext.Parameters.GetValue<int>("CustomerId");
return customerId > 0;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
// 离开视图时的清理(如保存数据)
SaveCurrentData();
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
// 进入视图时的初始化(如加载数据)
// IsNavigationTarget 为 false时 会执行OnNavigatedTo函数,true不会
var customerId = navigationContext.Parameters.GetValue<int>("CustomerId");
LoadCustomerData(customerId);
}
}IsNavigationTarget为false时导致旧页面累计导致内存泄漏问题。 解决办法:实现IRegionMemberLifetime,将属性 KeepAlive 设置为false 确保 deactivated 时移除视图。
特性
- 导航参数(NavigationParameters)
- 内置支持:navigationContext.Parameters["Key"] 获取值。
- 传递复杂对象
parameters.Add("UserData", new UserModel { Id = 1, Name = "Alice" });
- 导航日志(Journal)
- Prism 自动维护
IRegionNavigationJournal,支持GoBack()、GoForward()、ClearJournal() - 启用:在 Region 上设置 JournalNavigationTransitionInfo 或自定义过渡动画。
var journal = _regionManager.Regions["MainRegion"].NavigationJournal;if (journal.CanGoBack) journal.GoBack();
- Prism 自动维护
- 过渡动画
- 通过 INavigationTransitionProvider 自定义视图切换动画(如淡入淡出)。
- 示例:
RegionAdapterMappings.RegisterMapping(typeof(Selector), new CustomSelectorRegionAdapter());
- 错误处理
- 回调中捕获
NavigationResult
_regionManager.RequestNavigate("MainRegion", "MyView", result => { if (!result.Result) { // 处理错误,如显示消息框 MessageBox.Show(result.Error.Message); } }); - 回调中捕获
Prism导航命名覆盖机制详解
命名注册覆盖(Named Registration Override) 是 Prism Region 系统中一个重要的行为机制,指的是当多个视图使用相同名称注册到同一 Region 时,后续注册会覆盖之前注册的视图实例或映射关系。这种覆盖机制既是特性也是潜在的陷阱,需要理解其工作原理和控制方法。
覆盖机制的工作原理
Prism 的注册系统内部维护了两个关键的映射表:
- Region-View 映射(RegisterViewWithRegion):
Dictionary<string, Type/Func<object>>结构,键是 Region 名称,值是视图类型或创建委托。 - 导航键-视图映射(RegisterTypeForNavigation):
Dictionary<string, Type>结构,键是导航 URI,值是视图类型。
当发生重复注册时:
- 相同 Region + 相同视图名称:新注册直接替换旧映射。
- 相同导航键:新视图类型覆盖旧类型,后续导航会解析新类型。
- 不同名称/键:并存,不覆盖。
1. Navigation Registry的覆盖原理
Prism的导航系统基于一个内部的 ViewRegistry,它维护着 导航名称→视图类型 的映射关系:
// Prism内部的工作原理(简化版)
Dictionary<string, Type> _viewRegistry = new Dictionary<string, Type>();
// 当SettingModule注册时
_viewRegistry["ViewA"] = typeof(MorphoScan.SettingModule.Views.ViewA);
// 当CustomSettingModule注册时(后注册会覆盖)
_viewRegistry["ViewA"] = typeof(MorphoScan.CustomSettingModule.Views.ViewA); // 覆盖!示例:
// 第一次注册
_regionManager.RegisterViewWithRegion("MainRegion", typeof(MyView1), "CustomerView");
// 第二次注册 - 覆盖!
_regionManager.RegisterViewWithRegion("MainRegion", typeof(MyView2), "CustomerView");
/***************************************************************/
// 无名称注册,Prism 使用类型名作为内部键
_regionManager.RegisterViewWithRegion("MainRegion", typeof(MyView1));
_regionManager.RegisterViewWithRegion("MainRegion", typeof(MyView2)); // 覆盖 MyView1
/***************************************************************/
// 第一次注册
_container.RegisterTypeForNavigation<MyView1>("CustomerView");
// 第二次注册 - 覆盖!
_container.RegisterTypeForNavigation<MyView2>("CustomerView");
// 后续导航
_regionManager.RequestNavigate("MainRegion", "CustomerView"); // 解析为 MyView2
/***************************************************************/
//当 View Discovery 和 Navigation 同时作用时:
// 1. View Discovery 注册(静态)
_regionManager.RegisterViewWithRegion("MainRegion", typeof(StaticView));
// 2. Navigation 注册(动态)
_container.RegisterTypeForNavigation<DynamicView>("NavView");
// 3. 导航触发
_regionManager.RequestNavigate("MainRegion", "NavView"); // 注入 DynamicView,覆盖 StaticView