1. Prism框架MVVM项目实战概述
刚接触WPF开发时,最让我头疼的就是如何优雅地实现界面与逻辑的分离。直到遇到Prism框架,才发现原来MVVM模式可以如此清爽。这次我们就从零开始,用Prism搭建一个完整的WPF项目,我会把实际开发中那些文档里不会写的"坑"和技巧都分享出来。
Prism不是简单的MVVM工具库,它提供了一整套企业级应用开发方案。包含:
- 依赖注入容器(默认用Unity)
- 模块化开发支持
- 事件聚合器
- 导航服务
- 命令系统
这个实战项目我们会重点突破几个核心场景:如何用Region管理界面布局、ViewModel如何与View自动关联、模块之间如何松耦合通信。最终实现一个支持插件化扩展的桌面应用骨架。
2. 环境准备与项目搭建
2.1 开发环境配置
推荐使用VS2022+进行开发,需要安装:
- .NET 6.0 SDK(长期支持版本更稳定)
- Prism Template Pack扩展(搜索安装)
- NuGet包管理器
注意:Prism 8.x开始全面支持.NET Core/.NET 5+,如果还在用.NET Framework建议升级
2.2 创建项目骨架
- 在VS中选择Prism Blank App模板
- 项目命名如"PrismDemo"
- 选择.NET 6作为目标框架
- 取消勾选"启用Docker支持"(桌面应用不需要)
创建完成后会看到典型结构:
PrismDemo/ ├── Properties/ ├── App.xaml // 程序入口 ├── App.xaml.cs ├── Views/ // 默认MainWindow在这里 └── ViewModels/ // 对应ViewModel关键点解析:
- App.xaml中已经配置好PrismApplication基类
- Bootstrapper逻辑内置于App类中
- View和ViewModel约定优于配置
3. 核心功能实现
3.1 Region区域管理实战
Region是Prism的界面组织核心,我们先改造MainWindow.xaml:
<Window> <Grid> <ContentControl prism:RegionManager.RegionName="MainRegion" /> <ContentControl prism:RegionManager.RegionName="StatusRegion" Grid.Row="1" Height="30"/> </Grid> </Window>然后在App.xaml.cs中注册视图:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<MainView>("MainView"); containerRegistry.RegisterForNavigation<StatusView>("StatusView"); } protected override void ConfigureRegionAdapterMappings( RegionAdapterMappings regionAdapterMappings) { base.ConfigureRegionAdapterMappings(regionAdapterMappings); regionAdapterMappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>()); }动态切换视图的ViewModel代码:
public class MainViewModel : BindableBase { private readonly IRegionManager _regionManager; public DelegateCommand LoadViewCommand { get; } public MainViewModel(IRegionManager regionManager) { _regionManager = regionManager; LoadViewCommand = new DelegateCommand(ExecuteLoadView); } private void ExecuteLoadView() { _regionManager.RequestNavigate("MainRegion", "MainView"); } }踩坑记录:RegionManager需要通过构造函数注入,直接new实例会报空指针
3.2 模块化开发实践
- 新建类库项目"ModuleA"
- 添加Prism.Unity NuGet包
- 创建Module类:
public class ModuleAModule : IModule { public void OnInitialized(IContainerProvider containerProvider) { var regionManager = containerProvider.Resolve<IRegionManager>(); regionManager.RegisterViewWithRegion("MainRegion", typeof(ModuleAView)); } public void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<ModuleAView>(); } }- 在主项目引用模块,修改App.xaml.cs:
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<ModuleAModule>(); }模块化带来的好处:
- 功能解耦,各团队可并行开发
- 支持按需加载(修改ModuleCatalog配置)
- 便于插件化扩展
4. 进阶功能实现
4.1 事件聚合器实战
跨模块通信的经典场景:状态栏更新
- 定义事件消息类:
public class StatusMessage : PubSubEvent<string> { }- 发布方代码:
public class ModuleAViewModel { private readonly IEventAggregator _eventAggregator; public ModuleAViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; } private void UpdateStatus() { _eventAggregator.GetEvent<StatusMessage>() .Publish("模块A初始化完成"); } }- 订阅方代码:
public class StatusViewModel : IDisposable { private readonly SubscriptionToken _token; public StatusViewModel(IEventAggregator eventAggregator) { _token = eventAggregator.GetEvent<StatusMessage>() .Subscribe(OnStatusUpdated); } private void OnStatusUpdated(string message) { StatusText = message; } public void Dispose() { _token.Dispose(); } }4.2 对话框服务优化
Prism自带的对话框比较基础,我们扩展一个更易用的版本:
public interface ICustomDialogService { void ShowNotification(string title, string message); bool ShowConfirmation(string title, string message); } public class CustomDialogService : ICustomDialogService { private readonly IDialogService _dialogService; public CustomDialogService(IDialogService dialogService) { _dialogService = dialogService; } public void ShowNotification(string title, string message) { _dialogService.ShowDialog("NotificationDialog", new DialogParameters { { "title", title }, { "message", message } }, _ => { }); } }注册服务:
containerRegistry.RegisterSingleton<ICustomDialogService, CustomDialogService>();5. 调试与性能优化
5.1 常见问题排查
- View无法显示:
- 检查Region名称拼写是否一致
- 确认View已注册到容器
- 检查ViewModel是否继承BindableBase
- 导航失败:
- 检查目标View是否注册为可导航
- 确认RegionManager实例是通过注入获取
- 绑定失效:
- 输出窗口查看绑定错误
- 确认属性setter调用RaisePropertyChanged
5.2 性能优化技巧
- 模块按需加载:
moduleCatalog.AddModule<ModuleAModule>(InitializationMode.OnDemand);- 虚拟化数据列表:
<ListBox VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"/>- 弱引用事件订阅:
_eventAggregator.GetEvent<StatusMessage>() .Subscribe(OnStatusUpdated, ThreadOption.UIThread, keepSubscriberReferenceAlive: false);6. 项目扩展与部署
6.1 插件化架构实现
- 创建插件接口项目:
public interface IPlugin { string Name { get; } void Initialize(); }- 模块实现接口:
public class ModuleAModule : IModule, IPlugin { public string Name => "模块A"; public void Initialize() { // 初始化逻辑 } }- 动态加载插件:
var plugins = Directory.GetFiles(pluginsPath, "*.dll") .SelectMany(Assembly.LoadFrom) .SelectMany(x => x.GetTypes()) .Where(t => typeof(IPlugin).IsAssignableFrom(t));6.2 打包发布注意事项
- 单文件发布命令:
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true- 必备配置文件:
- appsettings.json(配置数据库连接等)
- NLog.config(日志配置)
- 安装程序制作:
- 使用WiX Toolset生成MSI
- 或使用Inno Setup制作安装包
经过这个完整项目的实践,最大的体会是:Prism的约定优于配置虽然初期学习曲线陡峭,但一旦掌握,开发效率会有质的飞跃。特别是当项目规模扩大后,模块化和松耦合的优势会越来越明显。