简介这份资源是基于WPF与C#开发的医院信息管理系统完整源码面向具备一定.NET基础、希望深入理解企业级桌面应用架构的开发者与计算机专业学生。项目以SQL Server 2012为数据存储采用分层设计将数据访问层、业务逻辑层与用户界面层有效隔离适合作为课程设计、毕业设计或WPF实战练手的参考范例。压缩包共273个文件约27.05MB其中77个C#源文件承载核心逻辑35个XAML文件定义界面布局94个PNG与10个JPG图片用于美化交互另含DLL库、项目文件、数据库文件及配置说明等结构完整。目前已有435人学习下载。通过研读源码读者可掌握WPF界面与C#逻辑的协作方式、XAML声明式布局技巧、SQL Server数据增删改查实现以及BLL与DAL分层解耦思路对构建规范的信息管理系统具有实际参考价值。1. 从一张住院登记单说起WPF C# 做医院信息管理系统到底在解决什么住院部早上八点最乱。护士站台面上摊着三样东西一张手写的入院登记单、一台装着老系统的电脑、还有一本用来核对床位的手册。医生开完医嘱要等护士手工录入药房发药要打电话确认出院结算时财务再翻一遍纸质病历。信息在四个科室之间来回搬每搬一次就多一次出错的机会。基于 WPF 和 C# 的医院信息管理系统要解决的就是把这条手工链路换成一条数据链路——患者从挂号、就诊、开方、收费到住院、出院状态在同一个数据库里流转每个角色只看到自己该看的那一屏。这个方向适合两类人一类是计算机专业做课程设计或毕业设计的学生需要一套能跑起来、结构说得清、答辩时经得起追问的完整工程另一类是做 C# 上位机、工控组态转医疗信息化的工程师手里有 WPF 数据绑定的底子想找一个业务复杂度够、又能体现 MVVM 分层价值的练手项目。医院信息管理系统HIS的业务边界比一般的管理系统宽它同时涉及并发多个窗口同时挂号、权限医生不能改收费、事务收费和发药要么都成要么都不成这些恰好是 WPF C# 这套技术栈能讲清楚的地方。热搜里 WPF 数据绑定、WPF MVVM、C# 上位机这几个词频繁出现说明很多人卡在「界面和数据怎么解耦」这一步。医院信息管理系统正好是个合适的载体一个患者列表要同时被挂号窗口、医生站、护士站引用如果每个窗口都写一遍查询逻辑改一个字段就要改三处。用 MVVM 把 ViewModel 抽出来界面只负责显示数据变更通过绑定自动刷新这才是这套源码值得拆开看的原因。下面从工程结构开始一步步把能复现的部分讲透。2. 工程分层与数据库设计先定骨架再写界面2.1 为什么医院信息管理系统必须分四层很多人拿到源码第一反应是找主窗口然后顺着按钮事件往下读读到最后发现业务逻辑全塞在Button_Click里改一个收费规则要翻五个文件。医院信息管理系统的业务规则变动频繁——医保比例调整、科室增减、药品目录更新——如果逻辑和界面缠在一起每次改动都是高风险操作。所以工程一开始就要分层常见做法是分四层View 层XAML 文件只放控件、样式、绑定表达式不写业务判断。ViewModel 层持有界面状态当前选中的患者、待缴费列表暴露命令Command调用 Service。Service 层业务规则集中地比如「挂号时校验该医生当日号源是否已满」「收费时校验处方是否已审核」。Data 层实体类、数据库访问、事务控制。这样分的好处是医生站的界面和护士站的界面可以复用同一个 Service规则只写一遍。热搜里提到的 WPF Command 定义、Delegate、Prism本质都是在解决 View 和 ViewModel 之间怎么通信的问题。如果不用框架用最朴素的ICommand实现也能跑通Prism 只是把导航、区域、依赖注入这些事标准化了课程设计级别不必强上。2.2 核心表结构患者、挂号、处方、收费四张主表数据库选 SQL Server 或 MySQL 都行课程设计常用 SQL Server因为和 C# 配合的System.Data.SqlClient或 Entity Framework 开箱即用。核心表不用多四张主表加两张关联表就能撑起主流程表名关键字段说明PatientPatientId, Name, Gender, IdCard, Phone, CreateTime患者基本信息IdCard 建唯一索引RegistrationRegId, PatientId, DeptId, DoctorId, RegTime, Status挂号记录Status 区分待诊/已诊/退号PrescriptionPrescId, RegId, DoctorId, DrugJson, AuditStatus处方药品明细可存 JSON 或拆子表ChargeChargeId, PrescId, Amount, PayStatus, PayTime收费记录PayStatus 控制发药权限建表时有两个细节容易忽略。一是IdCard要建唯一索引防止同一患者被重复建档否则后面统计人次会翻倍。二是Registration.Status用整型枚举而不是字符串0待诊,1已诊,2退号字符串比较在并发下容易出脏数据。药品明细如果拆子表PrescriptionDetail里存PrescId, DrugId, Quantity, UnitPrice查询时用 JOIN如果图省事存 JSON读取时在 Service 层反序列化但没法用 SQL 直接按药品统计看需求取舍。2.3 用 Entity Framework 建库的最小代码下面这段是 Data 层用 Entity Framework Code First 建上下文的骨架跑一次就能生成表// HisDbContext.cs public class HisDbContext : DbContext { // 连接字符串从 App.config 读避免硬编码 public HisDbContext() : base(nameHisConn) { } public DbSetPatient Patients { get; set; } public DbSetRegistration Registrations { get; set; } public DbSetPrescription Prescriptions { get; set; } public DbSetCharge Charges { get; set; } protected override void OnModelCreating(DbModelBuilder mb) { // 身份证唯一索引防止重复建档 mb.EntityPatient().HasIndex(p p.IdCard).IsUnique(); // 挂号与患者一对多 mb.EntityRegistration() .HasRequired(r r.Patient) .WithMany() .HasForeignKey(r r.PatientId); base.OnModelCreating(mb); } }逻辑说明base(nameHisConn)表示连接串写在App.config的connectionStrings节点里换数据库时只改配置不改代码。HasIndex是 EF 6.1 以后才有的方法如果用的是更早版本唯一约束要在数据库里手工建。HasRequired...WithMany定义了挂号必须挂在一个已存在的患者上插入挂号前如果 PatientId 不存在会直接抛异常这比在业务层写 if 判断更可靠。参数说明App.config里的连接串形如Data Source.;Initial CatalogHisDB;Integrated SecurityTrueIntegrated SecurityTrue表示用 Windows 身份验证部署到没有域环境的机器上要改成User Idsa;Passwordxxx。第一次运行前在程序入口调一次Database.SetInitializer(new CreateDatabaseIfNotExistsHisDbContext())库不存在时自动建存在时不动避免每次启动都重建丢数据。3. 挂号与收费主流程MVVM 绑定和事务怎么落地3.1 挂号窗口的 ViewModel 怎么写才不臃肿挂号窗口要显示科室列表、医生列表、号源余量还要响应「挂号」按钮。如果把这些都塞进一个 ViewModel文件会超过五百行。常见做法是按职责拆RegistrationViewModel只管当前挂号表单的状态和提交命令科室和医生的数据通过构造函数注入的IDeptService、IDoctorService获取。下面是一个精简版public class RegistrationViewModel : INotifyPropertyChanged { private readonly IRegistrationService _regService; private readonly IDeptService _deptService; public ObservableCollectionDept Depts { get; set; } public Dept SelectedDept { get; set; } public ObservableCollectionDoctor Doctors { get; set; } public Doctor SelectedDoctor { get; set; } public string PatientName { get; set; } public ICommand SubmitCommand { get; private set; } public RegistrationViewModel(IRegistrationService regService, IDeptService deptService) { _regService regService; _deptService deptService; Depts new ObservableCollectionDept(_deptService.GetAll()); SubmitCommand new RelayCommand(Submit, CanSubmit); } private bool CanSubmit() { // 科室、医生、姓名都填了才允许提交 return SelectedDept ! null SelectedDoctor ! null !string.IsNullOrWhiteSpace(PatientName); } private void Submit() { var reg new Registration { PatientName PatientName, DeptId SelectedDept.Id, DoctorId SelectedDoctor.Id, RegTime DateTime.Now, Status 0 }; _regService.Add(reg); // 内部做号源校验和事务 } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }逻辑说明ObservableCollection保证科室列表变化时界面自动刷新这是 WPF 数据绑定的基础。RelayCommand是自定义的ICommand实现CanSubmit返回 false 时按钮自动置灰不用手写button.Enabled false。Submit里不直接操作数据库而是交给_regService这样单元测试时可以替换成 mock。参数说明SelectedDept变化时应该触发医生列表刷新实际代码里要在 setter 里调OnPropertyChanged并重新加载Doctors。RelayCommand的CanExecuteChanged事件要挂到CommandManager.RequerySuggested上否则按钮状态不会实时更新。XAML 里绑定写法是Command{Binding SubmitCommand}ItemsSource{Binding Depts}SelectedItem{Binding SelectedDept}。3.2 收费和发药为什么必须放在一个事务里收费窗口点「确认收费」时要做两件事把Charge.PayStatus改成已支付同时把对应处方的AuditStatus改成可发药。如果第一步成功第二步失败患者交了钱却拿不到药这就是典型的分布式一致性问题。单库场景下用数据库事务就能解决public bool PayAndRelease(int prescId, decimal amount) { using (var db new HisDbContext()) using (var tran db.Database.BeginTransaction()) { try { var charge new Charge { PrescId prescId, Amount amount, PayStatus 1, PayTime DateTime.Now }; db.Charges.Add(charge); var presc db.Prescriptions.Find(prescId); if (presc null) throw new Exception(处方不存在); presc.AuditStatus 2; // 2 表示已收费可发药 db.SaveChanges(); tran.Commit(); return true; } catch (Exception ex) { tran.Rollback(); // 记录日志返回 false 让界面提示 return false; } } }逻辑说明BeginTransaction开启显式事务SaveChanges只是把变更推到事务里Commit才真正落库。任何一步抛异常都走Rollback保证收费记录和处方状态要么都变要么都不变。这里用Find而不是FirstOrDefault因为Find会先查本地缓存同一上下文里刚查过的处方不会重复查库。参数说明PayStatus和AuditStatus的枚举值要和数据库注释保持一致建议在代码里定义enum PayStatus { Unpaid0, Paid1 }不要到处写魔法数字。事务范围要尽量小BeginTransaction到Commit之间不要做网络请求或弹窗否则事务持有时间过长会锁表。如果收费和发药分属不同数据库那就不是单机事务能解决的需要引入消息队列做最终一致课程设计级别不必做到这一步。3.3 用数据绑定做患者列表的实时刷新医生站需要看到当前待诊患者列表护士站分诊后列表要自动更新。如果两个窗口各自查库护士站分诊完医生站不会知道。简单做法是医生站定时刷新但体验差。更好的做法是用ObservableCollection加一个共享的PatientQueueService分诊操作触发集合变更事件所有绑定该集合的界面同步刷新。热搜里 WPF 数据绑定、WPF 树形表格这些词落到这里就是ItemsControl或DataGrid绑定ObservableCollection集合增删时界面自动响应不用手动调Refresh。4. 避坑与排查医院信息管理系统源码里最容易翻车的五处4.1 现象挂号时提示「号源已满」但数据库里明明还有号原因号源余量是在内存里算的多个挂号窗口同时读同一个医生的号源都读到「还剩 1 个」然后都放行最后超卖。这是典型的并发读改写问题。解决把号源扣减放到数据库层用原子操作。不要先SELECT再UPDATE而是直接UPDATE Doctor SET Remain Remain - 1 WHERE DoctorId id AND Remain 0根据ExecuteNonQuery返回的影响行数判断是否成功返回 0 说明号源已满。EF 里可以用db.Database.ExecuteSqlCommand执行原生 SQL或者给Doctor表加行版本号做乐观锁。4.2 现象收费后发药窗口看不到待发药列表原因收费和发药用了两个不同的DbContext实例收费那个上下文改了AuditStatus但没提交或者提交了但发药窗口的查询走了缓存。解决确认PayAndRelease里tran.Commit()被调用。如果用了 EF 的二级缓存或自己写的缓存层收费成功后要主动清掉处方列表的缓存键。排查时先在数据库里直接查AuditStatus的值如果数据库已更新而界面没更新问题在界面刷新逻辑如果数据库没更新问题在事务提交。4.3 现象身份证号输入 18 位后保存报错「字符串截断」原因建表时IdCard字段设成了nvarchar(15)只够老版 15 位身份证新版 18 位存不下。解决IdCard统一用nvarchar(18)如果考虑港澳台证件或护照直接上nvarchar(32)。改字段长度用ALTER TABLE Patient ALTER COLUMN IdCard nvarchar(18)改之前确认没有依赖该字段的索引或约束有的话先删后建。这类问题在课程设计里很常见建表时字段长度宁可放宽不要卡死。4.4 现象WPF 界面卡死点任何按钮都没反应原因在 UI 线程里做了耗时操作比如挂号时同步查了几万条历史记录或者收费时调用了远程接口。WPF 的 UI 线程被占住界面自然不响应。解决耗时操作放Task.Run里查完用Dispatcher.Invoke回 UI 线程更新绑定属性。注意ObservableCollection的修改必须在 UI 线程做后台线程直接改会抛NotSupportedException。如果查询量大分页查每页 50 条不要一次ToList()全捞出来。4.5 现象换一台电脑部署后连不上数据库原因连接字符串里写的是Data Source.\SQLEXPRESS目标机器上装的是默认实例实例名不对或者用了Integrated SecurityTrue但目标机器没有对应的 Windows 账户权限。解决把连接串放到App.config里部署时只改配置文件。目标机器上先用 SSMS 或命令行sqlcmd -S localhost -U sa -P xxx确认能连上再启动程序。如果目标机器没有 SQL Server考虑换成 SQLite 做单机版连接串改成Data Sourcehis.dbEF 换System.Data.SQLite提供程序即可代码基本不用动。5. 从能跑到好用三个让这套源码加分的小技巧第一个技巧是给关键操作加操作日志。医院信息管理系统里「谁在什么时候改了处方」这类问题迟早会被问到与其事后翻数据库 binlog不如在 Service 层统一埋点。做法是定义一个OperationLog表字段包括UserId, Action, TargetId, Detail, LogTime在PayAndRelease、AddRegistration这些方法入口处写一条。不要在每个按钮事件里写那样会漏放在 Service 层所有调用方自动覆盖。日志写入用异步不要拖慢主流程。第二个技巧是用DataGrid的RowStyle做状态着色。待诊患者行显示浅黄已诊显示白色退号显示灰色。XAML 里写DataTrigger绑定Status字段值等于 0 时改Background。这样护士站扫一眼就知道哪些患者还没处理比看文字状态快得多。热搜里 WPF 界面设计、WPF 3D 动画看板这些词落到实际项目里界面加分项不是动画多炫而是信息密度和可读性。第三个技巧是给数据库加一个初始化脚本。课程设计答辩时经常要现场演示如果换一台机器就要手工建库建表很容易翻车。写一个Init.sql包含建库、建表、插入几条测试数据程序启动时检测表是否存在不存在就执行脚本。测试数据要覆盖边界一个身份证重复的患者验证唯一索引、一个号源为 0 的医生验证挂号拦截、一个已收费未发药的处方验证状态流转。-- Init.sql 片段插入测试数据 INSERT INTO Patient (Name, Gender, IdCard, Phone, CreateTime) VALUES (N张三, N男, N110101199001011234, N13800000000, GETDATE()); INSERT INTO Doctor (Name, DeptId, Title, Remain) VALUES (N李医生, 1, N主治医师, 0); -- 号源为 0用于测试挂号拦截逻辑说明N张三前缀 N 表示 Unicode 字符串避免中文乱码。Remain0的医生用来验证挂号时是否正确拦截。脚本要幂等重复执行不报错可以在INSERT前加IF NOT EXISTS判断。我自己的习惯是每接一个类似项目先把数据库脚本和分层骨架搭好再动界面。界面改十遍不心疼数据库改一遍要迁移数据。这套 WPF C# 的医院信息管理系统源码价值不在代码量而在它把「并发扣号源」「事务保收费发药一致」「MVVM 解耦界面」这几个真问题摆在了台面上。把这三点吃透换成其他管理系统也是同一套思路。希望帮到你。本文还有配套的精品资源点击获取