C# WPF 的 Prism 框架入门:核心概念与一个完整示例
前言
做过一段时间 WPF 开发的都知道,直接用"事件 + 代码后置(Code-Behind)"写界面,项目一大就容易失控:View 和逻辑耦合在一起、页面之间跳转要靠手动维护窗口、模块之间互相引用、单元测试难写。Prism 就是为解决这些问题而生的一个成熟 MVVM 框架。
本文面向 Prism 新手,先讲清楚它是什么、解决什么问题,再手把手带你搭一个能跑起来的完整示例(包含 Shell、区域、视图、ViewModel、命令和导航)。
一、什么是 Prism
Prism(原名 Prism Library / Composite Application Guidance)是由微软 Patterns & Practices 团队发起、现由社区维护的开源框架,专为 WPF、Xamarin.Forms、MAUI 等 XAML 技术栈提供 MVVM 与模块化支持。
它主要解决两类问题:
- 应用结构:用 MVVM 把界面(View)与逻辑(ViewModel)解耦,代码可测试、可维护。
- 大型应用拆分:通过"模块化(Modularity)"和"区域(Region)",把一个大应用拆成可独立开发、独立加载的模块,页面可以动态组合。
Prism 的核心价值可以概括为一句话:让你专注写业务,框架帮你管结构和生命周期。
二、Prism 核心概念速览
先用一张表快速认识 Prism 的各个子系统(后续文章会逐一深入):
- Bootstrapper(引导) :继承
PrismApplication,负责创建依赖注入容器、注册类型、创建主窗口(Shell)、加载模块,是应用的"启动入口"。 - 依赖注入(DI):内置容器(DryIoc 或 Unity),对象由容器创建和注入,天然解耦。
- Shell 与 Region(区域):Shell 是主窗口,里面定义若干"命名区域",其他视图可以动态注入到这些区域中。
- ViewModelLocator(视图模型定位) :按约定(
HomeView→HomeViewModel)自动给 View 绑定 ViewModel。 - BindableBase :实现
INotifyPropertyChanged的基类,用SetProperty写属性变更通知。 - DelegateCommand(命令) :实现
ICommand的命令,替代事件处理,支持CanExecute。 - Navigation(导航) :
IRegionManager.RequestNavigate在区域内切换视图,支持传参、回退。 - EventAggregator(事件聚合):模块间松耦合通信,类似发布/订阅。
- DialogService(对话框) :以 MVVM 方式弹窗,不直接
new Window。
本文示例会用到其中的 1、2、3、4、5、6、7 七项,足够你入门。
三、环境搭建
3.1 创建项目
- 用 Visual Studio 新建一个 WPF 应用(建议目标框架选 .NET 8,Prism 9.x 支持;.NET 6 可用 Prism 8.1)。
- 项目创建好后,默认是一个
App.xaml+MainWindow.xaml的传统结构。
3.2 安装 Prism
在 NuGet 包管理器安装:
- Prism.DryIoc(推荐,Prism 8.1+ / 9.x 的默认容器)。
- 或者 Prism.Unity(Prism 8.x 老项目常用;Prism 9 已移除 Unity 支持)。
安装后会自动引入 Prism.Core、Prism.Wpf 等依赖。本文以 Prism.DryIoc 为例。
四、应用启动流程(Bootstrapper)
Prism 应用与传统 WPF 应用最大的区别在于 App.xaml 和 App.xaml.cs。
4.1 App.xaml
根元素不再是 <Application>,而是 prism:PrismApplication,并且不要 写 StartupUri(主窗口由 Prism 自己创建):
xml
<prism:PrismApplication x:Class="PrismDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/">
<Application.Resources>
</Application.Resources>
</prism:PrismApplication>
4.2 App.xaml.cs
继承 PrismApplication,实现两个核心方法:
csharp
using Prism.Ioc;
using Prism.Regions;
using Prism.DryIoc; // 若用 Unity 则为 Prism.Unity
using System.Windows;
using PrismDemo.Views;
namespace PrismDemo
{
public partial class App : PrismApplication
{
// 创建并返回主窗口(Shell)
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
// 注册服务与可导航的视图
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<HomeView>();
containerRegistry.RegisterForNavigation<DetailView>();
}
// 初始化完成后,做一次初始导航,让 ContentRegion 显示首页
protected override void OnInitialized()
{
base.OnInitialized();
var regionManager = Container.Resolve<IRegionManager>();
regionManager.RequestNavigate("ContentRegion", nameof(HomeView));
}
}
}
要点说明:
CreateShell:返回主窗口,Prism 会自动Show()它。RegisterTypes:注册服务、以及用RegisterForNavigation<T>()注册可导航视图。OnInitialized:初始化完成后执行,这里做初始导航。
五、核心用法讲解
5.1 Shell 与 Region
Shell 是应用的外壳窗口,里面放置若干"区域"。区域是一个占位符(ContentControl、ItemsControl、TabControl 等),通过附加属性 RegionManager.RegionName 命名,之后视图可以注入到区域中:
xml
<Window x:Class="PrismDemo.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="Prism 入门示例" Height="450" Width="700">
<Grid>
<!-- 一个名为 ContentRegion 的内容区域,导航的内容会显示在这里 -->
<ContentControl prism:RegionManager.RegionName="ContentRegion"/>
</Grid>
</Window>
5.2 View 与 ViewModel 自动绑定
在 View 的根元素上加 prism:ViewModelLocator.AutoWireViewModel="True",Prism 会按约定自动查找并绑定 ViewModel:
- 约定:
Views.HomeView→ViewModels.HomeViewModel(把 "Views" 替换成 "ViewModels","View" 后缀替换成 "ViewModel")。
5.3 BindableBase
ViewModel 继承 BindableBase,用 SetProperty 写属性,自动触发属性变更通知:
csharp
private string _message = "欢迎使用 Prism";
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
5.4 DelegateCommand
用命令替代事件处理,命令在 ViewModel 构造时创建:
csharp
public DelegateCommand GreetCommand { get; }
public HomeViewModel(IRegionManager regionManager)
{
GreetCommand = new DelegateCommand(OnGreet);
}
private void OnGreet()
{
// 业务逻辑
}
在 XAML 中通过 Command="{Binding GreetCommand}" 绑定按钮。
5.5 Navigation
IRegionManager.RequestNavigate(区域名, 视图名, 参数) 在区域内切换视图,参数用 NavigationParameters 传递:
csharp
var parameters = new NavigationParameters { { "name", InputName } };
_regionManager.RequestNavigate("ContentRegion", nameof(DetailView), parameters);
接收方 ViewModel 实现 INavigationAware 接口,在 OnNavigatedTo 里取参数。
六、完整示例:一个带导航的小应用
下面给出全部源码。功能:主页输入名字,点"打招呼"用命令显示问候;点"去详情页"导航到详情页并带上名字,详情页可"返回主页"。
6.1 项目结构
text
PrismDemo/
├─ App.xaml
├─ App.xaml.cs
├─ Views/
│ ├─ MainWindow.xaml (Shell)
│ ├─ MainWindow.xaml.cs
│ ├─ HomeView.xaml
│ ├─ HomeView.xaml.cs
│ ├─ DetailView.xaml
│ └─ DetailView.xaml.cs
└─ ViewModels/
├─ HomeViewModel.cs
└─ DetailViewModel.cs
6.2 Shell:MainWindow.xaml
xml
<Window x:Class="PrismDemo.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="Prism 入门示例" Height="450" Width="700">
<Grid>
<ContentControl prism:RegionManager.RegionName="ContentRegion"/>
</Grid>
</Window>
csharp
// MainWindow.xaml.cs
using System.Windows;
namespace PrismDemo.Views
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
6.3 主页:HomeView + HomeViewModel
xml
<!-- HomeView.xaml -->
<UserControl x:Class="PrismDemo.Views.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<StackPanel Margin="24">
<TextBlock Text="Prism 入门示例" FontSize="26" FontWeight="Bold" Margin="0,0,0,20"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,12">
<TextBlock Text="请输入名字:" VerticalAlignment="Center" FontSize="14"/>
<TextBox Text="{Binding InputName, UpdateSourceTrigger=PropertyChanged}" Width="220" FontSize="14"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="打招呼" Command="{Binding GreetCommand}" Width="90" Margin="0,0,10,0"/>
<Button Content="去详情页" Command="{Binding GoDetailCommand}" Width="90"/>
</StackPanel>
<TextBlock Text="{Binding Message}" FontSize="16" Foreground="Green" Margin="0,24,0,0" TextWrapping="Wrap"/>
</StackPanel>
</UserControl>
csharp
// HomeView.xaml.cs
using System.Windows.Controls;
namespace PrismDemo.Views
{
public partial class HomeView : UserControl
{
public HomeView()
{
InitializeComponent();
}
}
}
csharp
// HomeViewModel.cs
using System;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using PrismDemo.Views;
namespace PrismDemo.ViewModels
{
public class HomeViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private string _inputName = string.Empty;
public string InputName
{
get => _inputName;
set => SetProperty(ref _inputName, value);
}
private string _message = "欢迎使用 Prism";
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
public DelegateCommand GreetCommand { get; }
public DelegateCommand GoDetailCommand { get; }
public HomeViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
GreetCommand = new DelegateCommand(OnGreet);
GoDetailCommand = new DelegateCommand(OnGoDetail);
}
private void OnGreet()
{
Message = $"你好,{InputName}!当前时间 {DateTime.Now:HH:mm:ss}";
}
private void OnGoDetail()
{
var parameters = new NavigationParameters { { "name", InputName } };
_regionManager.RequestNavigate("ContentRegion", nameof(DetailView), parameters);
}
}
}
6.4 详情页:DetailView + DetailViewModel
xml
<!-- DetailView.xaml -->
<UserControl x:Class="PrismDemo.Views.DetailView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<StackPanel Margin="24">
<TextBlock Text="详情页" FontSize="26" FontWeight="Bold" Margin="0,0,0,20"/>
<TextBlock FontSize="18">
<Run Text="你好,"/>
<Run Text="{Binding Name, Mode=OneWay}" FontWeight="Bold"/>
</TextBlock>
<TextBlock Text="这个值是通过导航参数从主页传过来的。" FontSize="14" Foreground="Gray" Margin="0,8,0,0"/>
<Button Content="返回主页" Command="{Binding GoBackCommand}" Width="100" HorizontalAlignment="Left" Margin="0,24,0,0"/>
</StackPanel>
</UserControl>
csharp
// DetailView.xaml.cs
using System.Windows.Controls;
namespace PrismDemo.Views
{
public partial class DetailView : UserControl
{
public DetailView()
{
InitializeComponent();
}
}
}
csharp
// DetailViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using PrismDemo.Views;
namespace PrismDemo.ViewModels
{
public class DetailViewModel : BindableBase, INavigationAware
{
private readonly IRegionManager _regionManager;
private string _name = string.Empty;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public DelegateCommand GoBackCommand { get; }
public DetailViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
GoBackCommand = new DelegateCommand(OnGoBack);
}
private void OnGoBack()
{
_regionManager.RequestNavigate("ContentRegion", nameof(HomeView));
}
// 导航到本页时,取出传递的参数
public void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters.ContainsKey("name"))
{
Name = navigationContext.Parameters.GetValue<string>("name");
}
}
public bool IsNavigationTarget(NavigationContext navigationContext) => true;
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
}
七、运行效果
- 启动应用,
ContentRegion区域显示主页,输入框输入"张三"。 - 点"打招呼",下方绿色文字显示:
你好,张三!当前时间 20:10:33。 - 点"去详情页",区域切换到详情页,显示"你好,张三",说明导航参数传递成功。
- 点"返回主页",区域又切回主页。
整个过程中,页面切换、参数传递、命令绑定、属性通知,全部由 Prism 的约定和 DI 自动完成,代码后置文件几乎为空。
八、总结
- Prism 通过
PrismApplication引导启动,用 DI 容器管理对象生命周期。 - Shell + Region 让页面可以动态组合,配合 Navigation 实现松耦合的页面切换。
ViewModelLocator+BindableBase+DelegateCommand构成了最核心的 MVVM 骨架,代码后置文件只需InitializeComponent()。- 本文示例只覆盖了入门部分,Prism 的模块化、EventAggregator、DialogService 等更高级能力留待后续。
掌握本文内容后,你就已经能用 Prism 搭出结构清晰的 WPF 应用了。