Skip to content

WPF Prism Rengion详解

约 3043 字大约 10 分钟

WPF

2025-09-18

在 WPF Prism 框架中,Region 是用于构建模块化、松耦合 UI 的核心概念。

本质上是一个“占位符”或“容器”,允许在运行时动态注入或替换视图(View,通常是 UserControl、DataTemplate 或自定义控件),从而实现 UI 的组合式开发。

适合大型应用,其中不同模块可以独立开发并动态组装到主界面(Shell)中。Prism 通过 RegionManager 类来管理和维护 Region 的集合

基本原理和工作机制

定义

Region通常附加到 WPF 的 UI 控件上,如ContentControlItemsControlTabControl等。这些控件充当 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就只加载第一个。

关键点:

  1. 它不是“立即添加”,而是 注册一个“创建回调”
  2. 每次 Region 创建,Prism 都会检查是否有针对该 Region 的注册 → 全部执行一遍
  3. 即使 Region 销毁了,注册记录依然存在于 RegionManager 中
  4. 你重复调用 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区别

维度RegisterViewWithRegionRegisterForNavigation (即 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&param2=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);
	}

}

IsNavigationTargetfalse时导致旧页面累计导致内存泄漏问题。 解决办法:实现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();
  • 过渡动画
    • 通过 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 的注册系统内部维护了两个关键的映射表

  1. Region-View 映射(RegisterViewWithRegion):Dictionary<string, Type/Func<object>> 结构,键是 Region 名称,值是视图类型或创建委托。
  2. 导航键-视图映射(RegisterTypeForNavigation):Dictionary<string, Type> 结构,键是导航 URI,值是视图类型。

当发生重复注册时:

  • 相同 Region + 相同视图名称:新注册直接替换旧映射。
  • 相同导航键:新视图类型覆盖旧类型,后续导航会解析新类型。
  • 不同名称/键:并存,不覆盖。

1. Navigation Registry的覆盖原理

Prism的导航系统基于一个内部的 ViewRegistry,它维护着 导航名称→视图类型 的映射关系:

// Prism内部的工作原理(简化版)
Dictionary<stringType_viewRegistry = new Dictionary<stringType>();

// 当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