资讯动态

WPF+Prism 8+MaterialDesign 图书管理系统开发实战

发布时间:2026/10/15 6:20:00 来源:尧图企业网站定制
说实话做桌面端管理系统的人多少都会有点“UI 不甘心”——明明业务逻辑不复杂但界面一出来就是灰扑扑的表格加按钮自己看着都提不起劲。我这次用WPF SQL Server MaterialDesign Prism 8做了一套图书管理系统最直观的感受是原来 .NET 桌面应用也能把界面做得像现代互联网产品一样清爽、干净、有层次感。这篇文章不聊空话我会把我从技术选型、项目结构、数据层到 UI 细节的实现过程以及踩过的一堆坑完整地写出来。这套系统面向的场景很典型一个小型图书室或内部资料室需要管图书档案、读者信息、借阅归还和简单的逾期统计。它不需要多高的并发但对界面交互、操作流畅度和后期维护性有要求。如果你正准备用 WPF 做管理系统或者想看看 Prism 8 在实际项目里怎么组织代码、MaterialDesign 怎么调教才不至于“一眼假”这篇应该能帮你省下不少试错时间。1. 为什么是 WPF SQL Server MaterialDesign Prism 8 这套组合1.1 桌面端技术选型的横向对比先说说为什么没有用更“省事”的方案。做图书管理系统最容易想到的无非是 WinForms、Web 管理系统、Electron 这几条路。我从实际体验出发把它们的优缺点摊开来看。方案开发效率UI 表现力打包体积数据访问长期维护WinForms较高较弱控件老旧小方便一般代码容易腐化Web 管理系统高但要做前后端分离强但需要额外做接口和部署依赖浏览器要走 HTTP前后端两套工程Electron高前端技术栈很强非常大需配合本地服务内存占用令人头疼WPF前期上手曲线陡强模板和样式自由度极高中等直接连库结构清晰则非常好维护WinForms 不是不能用但做过几个项目的人都会发现一旦遇到自定义样式、动画、数据绑定的复杂交互WinForms 的控件体系会不断逼迫你写底层绘图代码越写越痛苦。Electron 做桌面界面确实漂亮但装一个图书管理系统要带一个 Chromium 内核内存占用轻松上几百兆在小内存办公机上直接劝退。Web 版本则要解决部署和网络依赖内部小系统往往没有 IT 团队帮你维护服务器。WPF 的优势在于 XAML 描述界面、数据绑定驱动 UI、样式和模板可以做到彻底的自定义。配合 SQL Server 做数据存储整个系统可以是一个单机或局域网内的独立程序部署时只需要装 .NET 运行时和数据库不需要额外起 Web 服务。1.2 Prism 8 对个人项目是不是过度设计很多人一看 Prism 8 就紧张觉得那是企业级框架做个图书管理系统用不上。我的真实感受是如果你的项目超过四个页面、有多个业务模块、后续还想继续加功能Prism 带来的收益非常明显。Prism 8 解决的核心问题有三个依赖注入、MVVM 的规范化、页面导航。没有它WPF 项目通常的做法是每个窗口自己 new 一个 ViewModel窗口之间互相跳转写久了就是一阵乱麻。Prism 的模块化能力允许我把“图书档案”“读者管理”“借阅流通”各自封装成独立模块每个模块只管自己的 View 和 ViewModel然后用导航框架统一调度。这套机制落到图书管理系统上最直接的体现是主窗口的左侧菜单和右侧内容区完全解耦。点击菜单项时右侧 Region 根据导航名称动态加载对应的 View不需要在窗口代码里写一堆 if else 去切换页面。后面加一个“统计报表”模块只需要新增一个 Module 类在主程序注册一下菜单和区域自动就支持了原有的代码一行都不用改。2. 项目骨架模块划分与 Prism 8 导航设计2.1 图书管理系统先拆清楚功能边界动手写代码之前我把图书管理系统的功能拆成了四个模块图书档案管理图书的增删改查、封面图、分类、库存数量、ISBN 信息。读者管理读者的办卡、信息维护、借阅历史。借阅流通借书、还书、续借、逾期计算、借阅记录查询。统计与设置图书分类统计、借阅排行榜、系统参数的维护。这四个模块对应 Prism 里的四个 Module。每个 Module 内部由若干 View 和 ViewModel 组成比如“借阅流通”模块里有“借书操作页”“还书操作页”“借阅记录列表面”。模块之间不直接互相引用只在需要时通过 Prism 的导航参数或事件聚合器通信。一个系统如果一开始不划清模块边界后面每加一个功能都要动到主窗口的代码很快就变成意大利面条。Prism 的 Module 概念在这个场景里不是摆设它逼着你在做项目的时候就思考每段代码到底属于哪个业务域。2.2 Region 与 View 注入主窗体怎么组织主窗体的布局很简单左侧是一个 ListBox 或者 MaterialDesign 的 Drawer显示功能菜单右侧是一个 ContentControl 作为导航区域。Prism 的 Region 就是用来管理这个 ContentControl 的。在 App.xaml.cs 里我需要把自定义的 MainWindow 注册到 Prism 容器并指定一个 Regionprotected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigationBookListView, BookListViewModel(); containerRegistry.RegisterForNavigationReaderListView, ReaderListViewModel(); containerRegistry.RegisterForNavigationBorrowView, BorrowViewModel(); containerRegistry.RegisterForNavigationReturnView, ReturnViewModel(); containerRegistry.RegisterForNavigationStatisticsView, StatisticsViewModel(); } protected override void CreateShell() { var window Container.ResolveMainWindow(); window.Show(); }主窗口的 XAML 里最关键的是这一句ContentControl prism:RegionManager.RegionNameMainContentRegion /然后在菜单按钮的 Command 里调用导航private void NavigateCommand(string navigatePath) { RegionManager.RequestNavigate(MainContentRegion, navigatePath); }Prism 8 自带的RequestNavigate支持在导航路径里携带参数。比如点击“借阅记录”的某一行时需要同时把当前读者 ID 传给借阅历史页做法如下var parameters new NavigationParameters { { readerId, selectedReader.Id } }; RegionManager.RequestNavigate(MainContentRegion, BorrowHistoryView, parameters);在目标 ViewModel 中通过INavigationAware接口接收参数public void OnNavigatedTo(NavigationContext navigationContext) { var readerId navigationContext.Parameters.GetValueint(readerId); LoadBorrowHistory(readerId); }2.3 ViewModel 生命周期里容易忽视的细节Prism 的导航不是简单地切换界面它会触发 ViewModel 的OnNavigatedTo和OnNavigatedFrom。这里有个很关键的坑导航到同一个 View 多次时Prism 默认每次都会创建新的实例。如果你在图书列表页搜索了一个关键词然后切到读者管理再切回来列表会重新加载搜索状态丢失。解决方案有两种。第一种是在导航前设置NavigationParameters的IsNavigationInCache之类的行为第二种更实用把频繁切换的页面作为单例注册用RegisterForNavigation后配合容器生命周期管理或者在 ViewModel 里自己维护状态缓存。我在图书列表页用了比较简单的做法ViewModel 里保存一个_searchKeyword字段每次OnNavigatedTo时检查参数里有没有显式传搜索词如果没有就用缓存字段刷新列表。这样一来用户从其他菜单切回来时不会看到空白页面体验顺畅很多。如果你用的是 Prism 8 的 PubSubEvent要注意事件聚合器默认是弱引用订阅但如果你在订阅时用了 lambda 表达式捕获了 ViewModel 的成员就可能被编译器转换成强引用导致 ViewModel 无法被垃圾回收。后面踩坑部分我会专门展开。3. 数据层SQL Server 与仓储模式的落地3.1 EF Core 还是 Dapper我为什么选了 Dapper数据访问是管理系统绕不开的部分。很多 WPF 教程直接往 ViewModel 里写 ADO.NET图省事也有上 EF Core 的但配置和迁移对一个小系统来说有点重。我最后选的是Dapper 参数化 SQL的组合。拿图书列表查询为例EF Core 如果没做好 AsNoTracking 和分页很容易写出慢查询Dapper 则逼着你手写 SQL对索引和 WHERE 条件更敏感。图书管理系统这种场景绝大多数查询都是单表或者简单的多表 JOINDapper 完全够用而且性能非常稳定。选型时我也做了对比方案学习成本灵活性性能适用场景原生 ADO.NET低高但代码冗余高不适合快速开发EF Core中高中迁移方便中复杂对象模型、频繁改表结构Dapper低高高管理系统的 CRUD 与报表库存扣减、借阅记录插入这些操作用 Dapper 写一个事务非常直观。EF Core 的事务模型能用但 Session 和状态跟踪一旦出问题排查成本比 SQL 高不少。3.2 数据库初始化与连接管理图书管理系统需要几张核心表Books、Readers、BorrowRecords、Categories。建表 SQL 我放在项目里的Database文件夹发布时候用脚本执行。比较关键的是连接管理——Dapper 本身不会替你管理连接所以必须确保每次操作都要释放。我封装了一个简单的BookstoreDb类用来提供连接工厂public class BookstoreDb { private readonly string _connectionString; public BookstoreDb(string connectionString) { _connectionString connectionString; } public IDbConnection CreateConnection() { var connection new SqlConnection(_connectionString); connection.Open(); return connection; } }然后在仓储层使用public async TaskListBook SearchBooksAsync(string keyword) { using var connection _db.CreateConnection(); var sql SELECT b.*, c.CategoryName FROM Books b INNER JOIN Categories c ON b.CategoryId c.Id WHERE b.Title LIKE Keyword OR b.Author LIKE Keyword OR b.ISBN LIKE Keyword; var result await connection.QueryAsyncBook(sql, new { Keyword $%{keyword}% }); return result.ToList(); }using var保证连接在查询结束后第一时间归还连接池。这一点看起来很基础但我在做这个项目时一开始没注意发生过连接未释放导致的“连接池耗尽”错误后面踩坑部分会细说。Prism 的容器在这里也发挥了作用。我把BookstoreDb和各个仓储接口注册成单例或瞬态实例ViewModel 只需要声明构造函数参数Prism 会自动解析注入containerRegistry.RegisterSingletonBookstoreDb(); containerRegistry.RegisterIBookRepository, BookRepository();ViewModel 的构造函数无需关心数据库连接怎么创建直接面向仓储接口编程。图书管理系统的业务逻辑不复杂但这样一个看似多余的抽象的层在后期加单元测试和替换数据库时价值很大。3.3 异步化与 UI 线程的响应性WPF 里最忌惮的是 UI 线程被数据库查询阻塞。你点一个“查询”按钮如果同步执行 SELECT界面直接卡死几秒用户会觉得程序坏了。我在所有仓储方法上都加了asyncViewModel 里的命令用AsyncRelayCommand包装。Prism 8 本身提供了DelegateCommand但异步命令需要额外扩展。我用的是 Prism 自带DelegateCommand的Execute里写async void字段如下private AsyncRelayCommand _searchCommand; public AsyncRelayCommand SearchCommand _searchCommand ?? new AsyncRelayCommand(SearchAsync); private async Task SearchAsync() { IsBusy true; try { Books await _bookRepository.SearchBooksAsync(Keyword); } finally { IsBusy false; } }IsBusy绑定到界面的加载动画这样点击查询后书单区域会显示一个转圈动画而不是一片空白。这种小细节对用户感知影响巨大特别是图书列表几百上千条时。4. MaterialDesign “美到炸”的实操细节主题、控件与动画4.1 版本与资源字典配置MaterialDesign 在 WPF 里通常指两个包MaterialDesignThemes和MaterialDesignColors。安装之后App.xaml 的资源字典必须按顺序合并顺序错了很多控件样式直接不生效。我的 App.xaml 里最简配置如下Application.Resources ResourceDictionary ResourceDictionary.MergedDictionaries ResourceDictionary Sourcepack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml / ResourceDictionary Sourcepack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml / ResourceDictionary Sourcepack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml / ResourceDictionary Sourcepack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml / /ResourceDictionary.MergedDictionaries /ResourceDictionary /Application.Resources这里有个坑如果你把 Primary 颜色字典放到 Defaults 之后有些按钮的默认色可能不会更新。最好保持上面这个顺序先主题、再默认样式、最后颜色。4.2 配色与明暗主题的切换MaterialDesign 的默认主题偏 Material Design 2 风格风格上是“亮色 主色 强调色”。主色我用 DeepPurple深紫强调色用 Lime黄绿界面会显得有活力但并不刺眼。暗色模式可以通过BundledTheme配合Theme类动态切换var theme new Theme { BaseTheme BaseTheme.Dark, Primary SwatchHelper.Lookup[MyPalette.Primary.DeepPurple900], Secondary SwatchHelper.Lookup[MyPalette.Accent.LimeA700] }; var paletteHelper new PaletteHelper(); paletteHelper.SetTheme(theme);切换的时候整个窗口的按钮、输入框、卡片背景会联动更新。我在设置页加了一个“夜间模式”的开关绑定到这个代码虽然是个小功能但演示的时候非常加印象分。4.3 卡片式布局与 DataGrid 美化图书管理系统最核心的页面是列表页。原生 WPF 的 DataGrid 很丑直接用会拉低整体效果。MaterialDesign 提供了DataGrid的样式使用MaterialDesignDataGrid这个类或者直接给 DataGrid 设置 Style。但做得更显质感的方案是不用原生 DataGrid改用ItemsControl 卡片式数据模板。每个图书项做成一张卡片包含封面、书名、作者、分类、库存状态和操作按钮视觉效果比表格高一个档次。卡片的核心结构是这样的Border Style{StaticResource MaterialDesignCard} Margin0,0,0,8 Grid Margin16 Grid.ColumnDefinitions ColumnDefinition Width72/ ColumnDefinition Width*/ ColumnDefinition WidthAuto/ /Grid.ColumnDefinitions Border Width56 Height72 Background{StaticResource MaterialDesignPaperLight} CornerRadius4 TextBlock Text{Binding TitleShort} HorizontalAlignmentCenter VerticalAlignmentCenter/ /Border StackPanel Grid.Column1 VerticalAlignmentCenter TextBlock Text{Binding Title} FontWeightBold FontSize16/ TextBlock Text{Binding Author} Foreground{StaticResource MaterialDesignBodyLight} FontSize13/ /StackPanel StackPanel Grid.Column2 VerticalAlignmentCenter TextBlock Text{Binding StatusText} Foreground{Binding StatusBrush}/ Button Content借阅 Command{Binding DataContext.BorrowCommand, RelativeSource{RelativeSource AncestorTypeWindow}} CommandParameter{Binding}/ /StackPanel /Grid /BorderMaterialDesignCard会自带轻微的阴影和圆角卡片间距用 Margin 控制视觉上非常干净。这种布局方式比 DataGrid 更灵活而且完全符合 Material Design 的信息层级。加了卡片布局之后按钮样式也必须统一。MaterialDesign 的按钮默认自带圆角图标按钮用PackIcon控件配合Style{StaticResource MaterialDesignToolButton}鼠标悬停会有淡淡的涟漪效果细节到位。4.4 图标、过渡动画与对话框MaterialDesign 的图标库是MaterialDesignIcons通过PackIcon使用几乎覆盖所有常见场景。菜单栏、按钮、空状态提示都可以用它而不需要额外塞入图片资源。页面切换动画也是一个容易被忽视的提升点。Prism 8 本身不提供转场动画我默认没有做很花哨的转场只利用ContentControl的透明度动画ContentControl prism:RegionManager.RegionNameMainContentRegion ContentControl.Triggers EventTrigger RoutedEventContentControl.ContentChanged BeginStoryboard Storyboard DoubleAnimation Storyboard.TargetPropertyOpacity From0 To1 Duration0:0:0.15/ /Storyboard /BeginStoryboard /EventTrigger /ContentControl.Triggers /ContentControl这个动画只做了 150 毫秒的淡入不夸张、不延误操作感但切页面时明显比生硬的闪现舒服。过度动画这类东西要克制图书管理系统是工具型软件炫技动画做多了反而拖慢使用节奏。5. 图书管理核心业务功能的实现要点5.1 图书档案 CRUD 与输入校验图书信息录入界面有书名、作者、ISBN、分类、库存数量、价格等等。因为要绑定 MaterialDesign 的浮升标签文本框必填校验我用了 WPF 自带的ValidationRule配合IDataErrorInfo在 ViewModel 层做二次校验。在 XAML 里加一个模糊触发校验TextBox Style{StaticResource MaterialDesignOutlinedTextBox} TextBox.Text Binding PathBook.Title UpdateSourceTriggerLostFocus Binding.ValidationRules local:RequiredFieldValidationRule/ /Binding.ValidationRules /Binding /TextBox.Text /TextBox对应的RequiredFieldValidationRule只需要继承ValidationRule重写Validate方法返回ValidationResult.Error并提供错误信息。界面会显示红色边框和提示文字不需要人为控制样式切换。这里有一个体验细节UpdateSourceTrigger我用了LostFocus而不是PropertyChanged否则用户打字时每敲一个字符就会触发一次校验浮升标签旁边会不停闪红。等用户填完这个字段、聚焦离开再校验心理压力会小很多。5.2 借阅与归还的业务流程状态机与并发图书借阅的流程并不是简单的INSERT一条记录。一本书有库存数量和可借数量借出时要扣减归还时要加回同时要生成借阅记录。我把借书定义为三步检查读者是否存在、检查图书库存是否大于 0、插入借阅记录并减少库存。这三步必须放在同一个数据库事务里否则出现“记录插了但库存没扣”的数据错乱后面怎么查都对不上。Dapper 的事务写法public async Taskbool BorrowBookAsync(int readerId, int bookId) { using var connection _db.CreateConnection(); using var transaction connection.BeginTransaction(); try { var book await connection.QueryFirstOrDefaultAsyncBook( SELECT * FROM Books WHERE Id BookId FOR UPDATE, new { BookId bookId }, transaction); if (book null || book.Stock 0) { transaction.Rollback(); return false; } await connection.ExecuteAsync( UPDATE Books SET Stock Stock - 1, BorrowedCount BorrowedCount 1 WHERE Id BookId, new { BookId bookId }, transaction); await connection.ExecuteAsync( INSERT INTO BorrowRecords (ReaderId, BookId, BorrowDate, DueDate, Status) VALUES (ReaderId, BookId, BorrowDate, DueDate, Borrowed), new { ReaderId readerId, BookId bookId, BorrowDate DateTime.Now, DueDate DateTime.Now.AddDays(30) }, transaction); transaction.Commit(); return true; } catch { transaction.Rollback(); throw; } }FOR UPDATE是把对应图书的行锁住防止两个人同时借同一本书时出现库存扣成负数。归还流程类似但需要注意逾期处理。逾期费这件事我没有做得很复杂只在归还时判断DueDate是否早于当前时间弹出一个确认对话框提示用户“该读者逾期 X 天”然后继续走归还流程。逾期天数写入记录方便统计。5.3 搜索与分页的实现图书列表的搜索框支持书名、作者、ISBN 模糊匹配但这里有一个隐患直接用参数化 SQL 做LIKE %关键词%性能在小数据量下没问题但长远看还是应当做分页。我做了最简单的分页方式SQL 里用OFFSET ... ROWS FETCH NEXT ... ROWS ONLY配合页码和每页条数。Dapper 的查询参数增加Offset和PageSize。SELECT b.*, c.CategoryName FROM Books b INNER JOIN Categories c ON b.CategoryId c.Id WHERE b.Title LIKE Keyword OR b.Author LIKE Keyword OR b.ISBN LIKE Keyword ORDER BY b.Id OFFSET Offset ROWS FETCH NEXT PageSize ROWS ONLY;ViewModel 里维护CurrentPage和PageSize点“下一页”时重新查询。翻页时还要拉一下总条数来计算页数这里可以额外执行一个SELECT COUNT(*)。真正影响体验的是搜索的“防抖”。如果用户每次按键都触发查询图书量一大就会产生大量无效 SQL。我在 ViewModel 里给搜索命令加了一百毫秒的延迟private async Task SearchAsync() { using var cts new CancellationTokenSource(500); try { await Task.Delay(150, cts.Token); } catch (TaskCanceledException) { return; } Books await _bookRepository.SearchBooksAsync(Keyword, CurrentPage, PageSize); }用户在连续打字时每次新输入都会取消上一次延迟只有停顿时才真正发查询体验明显更跟手。6. 踩坑记录从编译失败到运行时卡顿的完整排查链路6.1 MaterialDesign 与 Prism 的版本兼容问题做这个项目时我遇到的最早的坑是 MaterialDesignThemes 的某个新版本需要 .NET 5 以上而 Prism 8 某条主线还在用旧的 .NET Core 3.1 目标框架。如果直接从 NuGet 拉最高版本编译时会报一堆程序集版本不匹配。排查过程是这样的项目建的是 .NET 6.0引用 Prism 8.1 和 MaterialDesignThemes 4.0编译本身能过但运行到资源字典加载时抛 XamlParseException提示找不到MaterialDesignCard资源。我一开始以为是 XAML 拼写问题检查了半天都正确。后来把 MaterialDesignThemes 降级到 3.2才恢复正常。这个问题的根本原因是 ResourceDictionary 的 Source 格式需要匹配具体的包版本。4.x 的样式资源路径比 3.x 多了一层。把 MaterialDesignThemes 锁在 3.x 的稳定版配合 Prism 8 的依赖交集是最省心的组合。如果你想用 4.x记得把 XAML 里的组件资源地址也一起升级不能混用。6.2 Prism 8 EventAggregator 的事件订阅内存泄漏借阅成功后我需要通知图书列表页刷新库存。因为图书列表和借阅操作页是不同的模块不方便直接用导航参数回传所以我用了 Prism 的EventAggregator发一个BorrowCompletedEvent。一开始的写法_eventAggregator.GetEventBorrowCompletedEvent().Subscribe(RefreshBooks);Prism 8 的这个订阅默认是弱引用理论上不会导致泄漏。但当我改用 lambda 表达式时_eventAggregator.GetEventBorrowCompletedEvent().Subscribe(async () await RefreshBooksAsync());问题就出现了lambda 捕获了this编译器生成一个委托弱引用机制不一定能正确处理导致页面关闭后 ViewModel 并没有被回收。我后来在OnNavigatedFrom里显式调用Unsubscribe才彻底解决。提示在 WPF Prism 项目里事件的订阅和退订一定要成对出现。尤其是页面生命周期较短的 ViewModelOnNavigatedFrom是退订事件的最佳位置。6.3 SQL Server 连接池耗尽问题项目跑到后期我偶尔会在日志里看到System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.第一反应是数据库连接数设太小。但排查后发现问题出在自己身上有几处仓储方法用了裸SqlConnection但在异常分支里没有关闭连接连接池被占满后续查询全部排队超时。修复方式是统一使用using var connection CreateConnection();并在CreateConnection里设置合理的连接超时时间。这个错误是最隐蔽的因为程序不是一开始就崩而是运行一段时间后随机卡死。如果你的应用也出现偶发卡顿建议先检查所有打开连接的地方是否都有Dispose。6.4 绑定失效和线程更新 UI 的坑WPF 里 UI 线程是不能被后台线程直接更新的。Dapper 的异步方法默认不会回到 UI 线程如果一个属性没有通过Dispatcher更新界面可能不刷新或者直接报NotSupportedException。我在第一次跑异步加载图书列表时列表控件一直空白但数据确实查回来了。因为Books属性是普通ObservableCollection在后台线程里给它AddWPF 直接忽略。后来我在 ViewModel 的属性绑定里确保所有 UI 相关属性的赋值都发生回 UI 线程最简单的方式是依赖AsyncRelayCommand自动捕获上下文——但 Prism 的DelegateCommand不保证这一点。所以全部命令我都改成了自定义的AsyncRelayCommand内部用await之后回到调用线程上下文这样属性刷新不会丢。6.5 发布部署时的运行时缺失问题dotnet publish默认可能不会把 SQL Server 的原生客户端相关的依赖全部打包出来如果部署到没有联网的机器上连接数据库会报Could not load file or assembly。这个问题的排查路径是本地跑没问题复制文件夹到另外一台机器跑不起来。后来我改用dotnet publish -r win-x64 --self-contained把运行时和依赖全部带上才稳定。图书管理系统这种内部工具部署目标机器的环境不可控自包含发布是最稳妥的方案。虽然体积大一倍但省去在客户机器上安装各种运行库的折腾。最后再分享一点个人体会做完这套系统我最大的感受是WPF 并没有过时它只是长期被固化的“企业级丑界面”印象拖累了。MaterialDesign 只是提供了样式基础真正决定界面质感的是你对布局间距、卡片层级、字体大小、反馈反馈这些细节的控制。技术栈不是 UI 丑的理由懒惰才是。如果你准备动手做一个类似的系统我建议不要一上来就追求把所有功能做完。先搭好 Prism 的壳子把图书列表这一条链路完整跑通——数据库建表、仓储查询、ViewModel 绑定、MaterialDesign 卡片展示——然后再去补其他模块。骨架顺了后面加功能只是复制粘贴的事。另外要特别提醒任何项目里数据库的事务和连接管理都要当成核心逻辑来对待。UI 做得再漂亮借阅时库存扣错了用户对你的评价只会是“这系统垃圾”。系统的可信度始终建立在数据正确性上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑