一.定义
Prism 是微软 Patterns & Practices 团队推出的模块化 MVVM 框架,专门用于 WPF/MAUI/Uno,主打大型桌面 / 移动端分层解耦开发
特点:
- 模块化:大型项目拆分多个独立模块(业务插件),按需加载、团队并行开发
- 依赖注入 DI:统一管理服务、ViewModel,不用手动 new 对象
- 区域导航 Region:页面容器自动管理视图切换(侧边栏、主内容区、弹窗统一管理)
- 事件聚合器 EventAggregator:模块之间解耦通信,不用强引用
- MVVM 标准化:内置 BindableBase、DelegateCommand,不用自己实现 INotifyPropertyChanged/ICommand
阶段一 手写入门
DI容器
第一种:你自己写的业务类 → 必须手动注册,否则无法注入
第二种:Prism 框架自带内置服务 → 已经自动注册,直接注入不用管
IRegionManager(区域管理器,你一直在用)IEventAggregator(事件总线)IDialogService(弹窗服务)INavigationService(导航服务)
运行顺序
步骤 1:在容器中注册你的页面(App.xaml.cs)
步骤 2:在界面上挖好坑,并命名区域(MainWindow.xaml)
步骤 3:构造函数注入区域管理器(MainWindow.xaml.cs)
步骤 4:呼叫管家,下达跳转命令(导航触发)
步骤 1:改造 App.xaml + App.xaml.cs(Prism 入口)
原生 WPF 入口是Application,Prism 必须继承PrismApplication,负责:DI 容器初始化、注册服务、启动主窗口 Shell。
App.xaml(删除原有 StartupUri)
<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>App.xaml.cs
using Prism; using Prism.Ioc; using PrismDemo.Views; using System.Windows; namespace PrismDemo { public partial class App : PrismApplication { // 1. 创建主窗口(Shell:整个程序根窗口) protected override Window CreateShell() { // 从DI容器拿Shell窗口,不要new return Container.Resolve<MainShell>(); } // 2. 类型注册:注册窗口、视图、服务到DI容器 protected override void RegisterTypes(IContainerRegistry containerRegistry) { // 注册主窗口 containerRegistry.RegisterWindow<MainShell>(); // 注册页面(后面Region导航用) containerRegistry.RegisterForNavigation<HomeView>(); // 注册全局服务(单例) containerRegistry.RegisterSingleton<IMessageService, MessageService>(); } } }步骤 2:创建根窗口 MainShell.xaml(Shell,程序主容器)
Shell 是整个程序的根窗口,定义 Region 区域(用来放页面切换)
<Window x:Class="PrismDemo.Views.MainShell" 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入门Demo" Height="450" Width="800"> <Grid> <!-- Prism区域:Name就是Region名称,用来导航跳转页面 --> <ContentControl prism:RegionManager.RegionName="MainRegion"/> </Grid> </Window>阶段二 Prism的内置MVVM体系
问题:
我们手写原生 MVVM 时,每个 ViewModel 都要重复实现
INotifyPropertyChanged接口、手写属性通知、手动实现ICommand命令,模板代码多、容易出错Prism 已经把这些重复逻辑全部封装成了现成的基类和工具类,我们只需要继承 / 调用,就能快速写出标准 MVVM 代码,专注业务逻辑。
1. ObservableObject:属性通知基类
1.1 它解决什么问题
原生 MVVM 中,每个属性都要手写「字段判等 → 赋值 → 触发通知」三步,重复度极高。ObservableObject是 Prism 提供的 ViewModel 基类,它内部已经实现了INotifyPropertyChanged接口,并封装了SetProperty方法,一行代码完成属性赋值 + 自动通知。
命名空间:Prism.Mvvm
1.2 手写 vs Prism 写法对比
原生手写属性(繁琐)
// 还要自己实现整个 INotifyPropertyChanged 接口 private string _userName; public string UserName { get => _userName; set { if (_userName != value) { _userName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserName))); } } }Prism ObservableObject 写法(简洁)
// ViewModel 只需要继承 ObservableObject public class MainViewModel : ObservableObject { private string _userName; // set 里直接调用 SetProperty public string UserName { get => _userName; set => SetProperty(ref _userName, value); } }1.3 SetProperty 内部做了什么
SetProperty(ref 字段, 新值)自动完成 3 件事:
- 值判等:新旧值相同则什么都不做,避免无效的 UI 刷新
- 字段赋值:值不同则给私有字段赋新值
- 触发通知:赋值后自动触发
PropertyChanged事件,UI 自动更新
1.4 进阶:赋值后执行额外逻辑
如果属性变更后需要追加操作(比如联动刷新命令状态),可用带回调的重载:
private string _password; public string Password { get => _password; set => SetProperty(ref _password, value, () => { // 密码变更后,自动刷新登录按钮的可用状态 LoginCommand.RaiseCanExecuteChanged(); }); }2. DelegateCommand 与 AsyncDelegateCommand:命令封装
2.1 前置认知:ICommand 的本质
WPF 命令必须实现ICommand接口,核心是 3 个成员:
Execute:命令执行的业务逻辑CanExecute:返回 bool,控制命令是否可执行(按钮灰 / 可用)CanExecuteChanged:通知系统重新检查命令可用状态
Prism 把接口实现全部封装好了,同步用DelegateCommand,异步用AsyncDelegateCommand。
命名空间:Prism.Commands
2.2 DelegateCommand:同步命令
基础无参数命令
// 1. 定义命令属性 public DelegateCommand SayHelloCommand { get; } // 2. 构造函数中初始化,传入执行方法 public MainViewModel() { SayHelloCommand = new DelegateCommand(SayHello); } // 3. 命令执行的业务方法 private void SayHello() { MessageBox.Show("Hello Prism!"); }XAML 绑定和原生命令完全一致:
<Button Content="打招呼" Command="{Binding SayHelloCommand}" />带 CanExecute 的命令
控制按钮可用状态,比如「保存」按钮只有内容修改后才能点击
public DelegateCommand SaveCommand { get; } private bool _isModified; public bool IsModified { get => _isModified; set => SetProperty(ref _isModified, value); } public MainViewModel() { // 第二个参数传入 CanExecute 判断方法 SaveCommand = new DelegateCommand(Save, CanSave); } private void Save() { /* 保存逻辑 */ } // 返回 true 按钮可用,false 按钮禁用 private bool CanSave() => IsModified;注意:原生写法中,
IsModified变化后,按钮不会自动刷新状态,需要手动调用SaveCommand.RaiseCanExecuteChanged()。
ObservesProperty:自动刷新命令状态
Prism 提供ObservesProperty方法,监听指定属性,属性一变就自动刷新 CanExecute,不用手动调用。
public MainViewModel() { SaveCommand = new DelegateCommand(Save, CanSave) .ObservesProperty(() => IsModified); // 监听 IsModified 属性 } //支持链式监听多个属性: LoginCommand = new DelegateCommand(Login, CanLogin) .ObservesProperty(() => UserName) .ObservesProperty(() => Password);泛型命令 DelegateCommand<T>
如果需要给命令传参数(对应 XAML 的CommandParameter),用泛型版本。
// 指定参数类型为 int public DelegateCommand<int> DeleteCommand { get; } public MainViewModel() { DeleteCommand = new DelegateCommand<int>(Delete); } private void Delete(int id) { // 根据 id 执行删除逻辑 }XAML 传参:
<Button Content="删除" Command="{Binding DeleteCommand}" CommandParameter="1001" />2.3 AsyncDelegateCommand:异步命令
如果给DelegateCommand传async void方法,会有两个问题:
- 异常无法被正常捕获,容易导致程序崩溃
- 无法跟踪执行状态,不能防止重复点击
AsyncDelegateCommand是 Prism 专门为异步场景封装的,支持async Task方法,还自带IsExecuting执行状态属性。
public AsyncDelegateCommand LoadDataCommand { get; } public MainViewModel() { LoadDataCommand = new AsyncDelegateCommand(LoadDataAsync); } // 执行方法是 async Task,不是 async void private async Task LoadDataAsync() { await Task.Delay(2000); // 模拟耗时加载 // 加载完成后的业务逻辑 }防止重复点击(常用技巧)
异步任务执行期间自动禁用按钮,执行完自动恢复:
public MainViewModel() { LoadDataCommand = new AsyncDelegateCommand(LoadDataAsync) .ObservesCanExecute(() => IsNotLoading); } // 执行中 IsExecuting=true → IsNotLoading=false → 按钮自动禁用 private bool IsNotLoading => !LoadDataCommand.IsExecuting;3.CompositeCommand:复合命令(批量操作神器)
3.1应用场景
上位机开发高频使用:比如主界面「全部启动」按钮,点击后同时启动设备 A、设备 B、设备 C 的启动命令;还有「全部停止」「批量初始化」等场景。
核心逻辑:复合命令是一个「命令容器」,多个子命令注册进去后,触发复合命令时,所有子命令一起执行
3.2 实操步骤
第一步:定义全局复合命令
通常放在静态类中,方便各个子模块访问
public static class GlobalCommands { // 全局「全部启动」复合命令 public static CompositeCommand AllStartCommand { get; } = new CompositeCommand(true); // 传 true 表示监控子命令状态,子命令变了自动刷新复合命令可用状态 }第二步:子 ViewModel 注册命令
比如设备 A 的 ViewModel 把自己的启动命令注册进去:
public class DeviceAViewModel : ObservableObject { public DelegateCommand StartCommand { get; } public DeviceAViewModel() { StartCommand = new DelegateCommand(Start); // 注册到全局复合命令 GlobalCommands.AllStartCommand.RegisterCommand(StartCommand); } private void Start() { // 设备A启动逻辑 } }第三步:主界面绑定复合命令
<Button Content="全部启动" Command="{x:Static local:GlobalCommands.AllStartCommand}" />3.3 注意事项
- ViewModel 销毁 / 页面关闭时,调用
UnregisterCommand注销命令,避免内存泄漏 - 默认规则:所有子命令都可执行时,复合命令才可执行
4. View 与 ViewModel 自动绑定(命名约定)
4.1 作用
MVVM 中 View 需要通过DataContext关联 ViewModel 才能绑定数据。如果每个页面都手动写DataContext = new XXXViewModel(),耦合度高,也不支持依赖注入。
Prism 提供ViewModelLocator(视图模型定位器),按照命名约定自动找到对应 ViewModel 并赋值DataContext
4.2 默认命名约定
遵循「约定大于配置」:
- View 放在
Views文件夹,类名以View结尾,如MainView.xaml - ViewModel 放在
ViewModels文件夹,类名以ViewModel结尾,如MainViewModel - 自动配对:
MainView→MainViewModel
4.3 开启自动绑定
在 View 的 XAML 中添加附加属性即可:
- 引入 Prism 命名空间
- 开启自动绑定
<Window x:Class="PrismDemo.Views.MainView" 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" Title="主窗口" Height="450" Width="800"> <Grid> <TextBox Text="{Binding UserName}" /> </Grid> </Window>运行后 Prism 会自动创建MainViewModel实例,赋值给当前 View 的DataContext
5. 手动绑定 ViewModel 的两种方式
自动绑定是主流方案,特殊场景(打破命名约定、自定义注入逻辑)需要手动绑定,常用两种方式:
方式一:构造函数注入(依赖注入方式)
Prism 自带依赖注入容器,ViewModel 会被容器管理。在 View 的构造函数中直接注入 ViewModel 实例,再赋值给DataContext。
public partial class MainView : Window { public MainView(MainViewModel viewModel) { InitializeComponent(); // 把容器注入的 ViewModel 赋值给 DataContext DataContext = viewModel; } }优点:强类型、显式可控,适合需要在 View 构造中做额外处理的场景。
方式二:ViewModelLocator 手动指定类型
不想遵守命名约定时,可强制指定 View 对应的 ViewModel 类型。
<Window x:Class="YourApp.Views.CustomView" xmlns:prism="http://prismlibrary.com/" xmlns:vm="clr-namespace:YourApp.ViewModels" prism:ViewModelLocator.AutoWireViewModel="True" prism:ViewModelLocator.ViewModelType="{x:Type vm:MyCustomViewModel}">