WinForm住院管理系统源码解析:从数据库还原到业务模块
简介这份winform版住院管理系统基于C#与SQL Server数据库采用C/S架构开发适合C#课程设计、毕业设计及需要快速搭建桌面管理系统的开发者。系统覆盖权限、科室、用户、病人、住院、治疗、费用七大管理模块每个模块均包含查询、新增、编辑、删除等操作并提供金额设置、登录退出与主界面功能链条完整可完整演示医院住院业务的后台操作流程。资源共112个文件包含47个C#源文件、17个resx界面资源、16个resources编译资源另有可执行程序、配置文件和数据库文件winyiyuan.mdf压缩包仅2.59MB结构简明便于直接对照学习数据库文件内包含数据表和初始数据可直接附加使用。已有606人浏览学习源码内置管理员账号admin开发环境为VS2010SQL Server2008代码与数据库齐全适合用来练习C/S模式下的窗体界面设计、数据表关联以及增删改查实现。1. 拿这套 WinForm 住院管理系统当骨架先看清它的边界再动手做课程设计或者给小型医疗机构搭内部管理系统最怕的不是写不出代码而是从零开始画窗体、建表、调权限光登录和主界面就能耗掉两三天。这套 WinForm 住院管理系统值得下是因为它把 C/S 模式下的常见模块一次给齐了权限、科室、用户、病人、住院、治疗、费用、金额设置外加登录和主界面九个功能点全部走增删改查闭环。开发环境是 VS2010 SQLServer2008C# 语言数据库文件叫 winyiyuan.mdf管理员账号 admin / admin。适合三类人做毕业设计或课程设计的在校生、想快速搭一套桌面端管理系统的开发新手、需要参考经典 C/S 分层写法的在职程序员。它不是什么现代化框架但作为骨架足够你在上面换皮、加功能、改业务。2. 源码结构与数据库还原先让 winyiyuan.mdf 跑起来拿到压缩包别急着打开 .cs 文件先把目录结构和数据库恢复这件事理清楚。很多新手卡在第一步——代码看着都懂但程序一运行就报数据库无法连接问题多半出在 mdf 文件没挂上或者连接字符串写错了。2.1 解压后的文件清单辨别源码、配置与生成物解压之后你会看到一批文件我按用途给你分个类避免对着文件夹发懵。文件/目录类型说明form.csproj项目文件入口双击或从 VS2010 打开App.config / form.exe.config配置文件连接字符串和运行时配置exe.config 是编译后生成的bingren.Designer.cs窗体设计器代码病人管理窗体的控件布局自动生成zhuyuan.Designer.cs窗体设计器代码住院管理窗体zhiliao.Designer.cs窗体设计器代码治疗管理窗体mainform.Designer.cs窗体设计器代码主界面窗体yonghu.Designer.cs窗体设计器代码用户管理窗体form.vshost.exe.config运行配置VS 调试宿主进程用的配置ResolveAssemblyReference.cache缓存文件VS 解析程序集引用的缓存可删可忽略GenerateResource.Cache缓存文件资源生成的缓存不用管winyiyuan.mdf数据库文件核心数据需要附加到 SQL Server注意几个细节。.csproj才是项目入口别双击某个.Designer.cs去改布局后面我会专门讲 Designer.cs 的坑。.vshost.exe.config是调试用的真正发布后读的是form.exe.config。那两个 cache 文件是 VS 生成的中间产物删掉不影响编译属于强迫症可以清理的东西。2.2 SQLServer 2008 附加数据库mdf 还原的两条路径数据库文件是winyiyuan.mdf没有分离日志文件.ldf也问题不大SQL Server 在附加时会自动重建日志。两条路任选。第一条路图形界面操作。打开 SQL Server Management Studio右键数据库→附加→添加选中winyiyuan.mdf确认后点确定。如果提示缺少日志文件选 mdf 后把下方列表里的.ldf行删掉再确定SQL Server 会自己重建日志。第二条路T-SQL 命令。适合习惯脚本化操作的人在 SSMS 新建查询窗口执行USE [master] GO EXEC sp_attach_db dbname Nwinyiyuan, filename1 ND:\HIS\winyiyuan.mdf GOsp_attach_db是 SQL Server 2008 及之前版本推荐的附加存储过程2012 以后推荐用CREATE DATABASE ... FOR ATTACH但那套语法在这台 2008 环境里没必要。filename1路径一定要指向 mdf 实际所在位置路径里有空格或者中文建议先用单引号包住整体。附加成功后展开数据库列表应该能看到winyiyuan里面包含用户表、住院表、治疗表、费用表等业务表。在动手改代码之前先在 SSMS 里SELECT TOP 100 * FROM bingren之类的查询跑一遍确认表里有数据、主键和字段名跟自己预期一致这一步能省掉后面大量的排查时间。2.3 连接字符串与 App.config让程序找到数据库附加好数据库只是第一步程序还要知道去哪连。这套系统的连接字符串写在App.config里格式上走的是 SqlClient 那一套。?xml version1.0 encodingutf-8 ? configuration connectionStrings add namehisDB connectionStringData Source.\SQLEXPRESS;Initial Catalogwinyiyuan;User IDsa;Password123456 providerNameSystem.Data.SqlClient / /connectionStrings /configurationData Source.\SQLEXPRESS是实例名本地默认实例就写.或localhost命名实例要写成机器名\实例名。Initial Catalog对应刚才附加的数据库名。账号密码用sa还是 Windows 身份验证取决于你 SQL Server 的认证模式。在form.cs或App.config对应的业务类里通常通过ConfigurationManager.ConnectionStrings[hisDB].ConnectionString去读。改配置的时候注意一个非常容易翻车的点程序调试时读的是App.config但如果你直接运行bin\Debug下的 exe读的是form.exe.config。所以改了App.config发现没生效八成是忘了同步form.exe.config或者项目没有重新生成。3. 登录与主窗体权限校验和模块入口的落地写法系统跑起来后第一个界面是登录窗体这一章拆开看它的校验逻辑和主窗体的权限控制。很多初学者拿到源码喜欢直接改界面但登录和主窗体的代码结构才是这套系统的骨架读懂了它其他模块的套路就全通了。3.1 登录窗体的校验逻辑admin/admin 背后的一次查询管理员账号 admin、密码 admin这个默认写在源码里或者通过初始化脚本写进数据库的。登录窗体的校验本质就是一次带条件的 SQL 查询我见过最朴素的写法是这样的private void btnLogin_Click(object sender, EventArgs e) { string connStr ConfigurationManager.ConnectionStrings[hisDB].ConnectionString; string sql SELECT COUNT(*) FROM yonghu WHERE usernamename AND passwordpwd; using (SqlConnection conn new SqlConnection(connStr)) { SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.AddWithValue(name, txtUsername.Text.Trim()); cmd.Parameters.AddWithValue(pwd, txtPassword.Text.Trim()); conn.Open(); int count (int)cmd.ExecuteScalar(); if (count 0) { this.DialogResult DialogResult.OK; this.Close(); } else { MessageBox.Show(用户名或密码错误); } } }AddWithValue在这个场景够用但注意我加了Trim()去掉首尾空格能避开密码对但进不去的低级错误。ExecuteScalar只取第一行第一列适合这种只关心 count 的查询。登录成功后把当前用户名存到全局变量或主窗体属性里后续的权限判断要用到它。这套代码里有几个值得你抄作业的点using包裹连接对象确保释放、参数化查询避免 SQL 注入、DialogResult控制窗体关闭时机。我见过不少人写成字符串拼接 SQL在课程设计答辩时被老师问住参数化写法是底线。3.2 主窗体菜单与权限控制按钮显隐的常见实现登录成功进主界面mainform这里通常会根据当前用户角色控制菜单和按钮的显隐。常见做法是登录时把角色 ID 或权限级别存到一个公共变量主窗体加载时逐个判断。private void MainForm_Load(object sender, EventArgs e) { // currentRole 在登录成功后赋值0管理员1普通用户 if (GlobalVar.CurrentRole 1) { btnUserManage.Visible false; // 用户管理只对管理员开放 btnPowerManage.Visible false; // 权限管理同上 } else { btnUserManage.Visible true; btnPowerManage.Visible true; } }这里的核心是菜单显隐不等于权限安全真正的权限要在各个业务窗体的增删改操作里再校验一遍。这套系统源码里如果只做了界面层控制你也别意外——课程设计级别的项目大多如此。真要做硬权限可以在每个窗体的Load事件里再查一次当前用户的权限码或者在业务类的写操作前校验。另外吐槽一下网上经常搜到winform 界面美化教程主窗体这里最容易改出效果的是BackColor、按钮的FlatStyle、还有那个菜单折叠的箭头是怎么绘制的——其实就是ToolStripMenuItem的DropDownArrow样式或者你自绘箭头图标。想动手美化可以从这几个控件属性改起不影响业务逻辑。3.3 Designer.cs 文件能改吗可视化设计与手写代码的边界.Designer.cs是窗体设计器自动生成的代码里面全是this.btnSave.Location new System.Drawing.Point(...)这类布局代码。你直接在 VS 的窗体设计器里拖控件改动会被写进这个文件但如果你手改这个文件稍有不慎就会和设计器不同步出现窗体打不开或者控件重复定义的编译错误。我的习惯是能用设计器拖的绝不动 Designer.cs 手写实在要加控件在设计器里拖完再右键查看代码去写事件。还有一个常见问题——ResolveAssemblyReference.cache报错导致整个项目打不开这不是源码的问题是 VS 的引用缓存坏了删掉这个文件重新生成就行不会丢任何代码。这种玄学问题遇到一次就知道不是个大事。4. 核心业务模块怎么读病人、住院、治疗、费用四张表的联动八个管理模块看着多实际核心业务就一条线病人入院 → 登记住院 → 安排治疗 → 结算费用。金额设置表是参数表为费用计算提供单价。这一章就顺着这条线拆代码。4.1 病人管理到住院登记从列表到主从表的操作流程病人管理窗体bingren.Designer.cs典型的布局是DataGridView显示病人列表下方一排按钮做增删改查。核心操作逻辑private void btnAdd_Click(object sender, EventArgs e) { string sql INSERT INTO bingren(bingrenName, sex, age, phone) VALUES(name, sex, age, phone); using (SqlConnection conn new SqlConnection(connStr)) { SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.AddWithValue(name, txtName.Text.Trim()); cmd.Parameters.AddWithValue(sex, cmbSex.Text); cmd.Parameters.AddWithValue(age, txtAge.Text.Trim()); cmd.Parameters.AddWithValue(phone, txtPhone.Text.Trim()); conn.Open(); cmd.ExecuteNonQuery(); LoadData(); // 重新加载列表 } }参数按文本框顺序一一对应注意cmbSex是下拉框取的是Text属性也就是用户选中的显示文字。LoadData()是窗体里自定义的刷新方法把DataGridView重新绑定一遍写完增删改查后记得调用它不然新增的数据要手动刷新才能看到。此外病人表通常是主表住院表通过病人 ID 关联所以bingren表要有自增主键添加后要把新主键取出来才能拿去做住院登记。4.2 治疗与费用计算金额设置表如何参与运算治疗管理zhiliao和费用管理是这套系统里最有业务味道的部分。一次治疗的收费 治疗项目单价 × 次数 药品费单价存在金额设置表里。代码逻辑大致分成两步先从金额表读出单价再算出总额写入费用表。private decimal GetPrice(string itemName) { string sql SELECT price FROM jineshezhi WHERE itemNamename; using (SqlConnection conn new SqlConnection(connStr)) { SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.AddWithValue(name, itemName); conn.Open(); object result cmd.ExecuteScalar(); return result null ? 0 : Convert.ToDecimal(result); } } // 治疗登记时计算费用 private void btnSaveTreat_Click(object sender, EventArgs e) { decimal unitPrice GetPrice(cmbItem.Text.Trim()); int times Convert.ToInt32(txtTimes.Text.Trim()); decimal total unitPrice * times Convert.ToDecimal(txtDrugFee.Text.Trim()); string sql INSERT INTO zhiliao(patientId, itemName, unitPrice, times, totalFee) VALUES(pid, item, price, times, total); using (SqlConnection conn new SqlConnection(connStr)) { SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.AddWithValue(pid, currentPatientId); cmd.Parameters.AddWithValue(item, cmbItem.Text.Trim()); cmd.Parameters.AddWithValue(price, unitPrice); cmd.Parameters.AddWithValue(times, times); cmd.Parameters.AddWithValue(total, total); conn.Open(); cmd.ExecuteNonQuery(); } }GetPrice里ExecuteScalar返回 object强转前先判空这是防止金额表里没配这个项目导致 NullReference 的兜底。费用计算的精度问题要在 SQL Server 的 decimal 字段里控C# 侧用decimal而不是double否则浮点误差会让你在期末答辩时被问得下不来台。金额设置管理里改单价会影响后续新登记的治疗费但不会追溯改历史记录这个业务边界你要清楚新人容易误以为改了参数历史数据全变。4.3 SQL 语句与参数化查询从源码里抄作业的正确姿势整套系统的数据访问基本是 SqlConnection SqlCommand DataSet/DataTable 的老三样个别窗体可能用 SqlDataAdapter 填充 DataGridView。这是典型的 ADO.NET 直写风格没有三层架构也没有 ORM。我建议你从这套源码里抄两个作业一是参数化查询的统一写法二是 DataGridView 绑定的套路。但不要照搬它的连接管理方式——如果发现某些窗体是全局SqlConnection且长生命周期那是一种不好的示范SQL Server 的连接池本身会管理复用你不需要自己持有一个长连接。正确的抄法是每个操作using一个短连接让连接池去复用底层物理连接这也是网上搜winform dapper 依赖注入时会看到的主流实践——虽然这套老系统没用 Dapper但你把它的SqlConnection换成 Dapper 的QueryT半天就能把读操作全部重构掉。想练手又不破坏原系统的建议复制一份代码另起工程改造。5. 避坑与排查还原、编译、运行三关的实战问题老项目的坑从来不在功能逻辑上而在环境。下面五条是从还原到跑通全程最常踩的我按现象 → 原因 → 解决给你一条条列清楚。5.1 附加数据库失败文件权限与版本不匹配现象SSMS 里附加winyiyuan.mdf报错提示无法打开物理文件或者提示数据库版本高于当前实例。原因两个情况最常见。一是 mdf 文件放在了解压目录或桌面SQL Server 服务账号没有那个文件夹的读取权限二是这个 mdf 可能是更高版本 SQL Server比如 2012/2014生成的2008 实例无法附加高版本数据库文件。解决先把 mdf 复制到C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA目录下再附加右键文件属性给SQLServer2008MSSQLUser$你的机器名$MSSQLSERVER加完全控制权限。如果是版本问题去装一个 SQL Server 2012 以上实例附加成功后用任务 → 生成脚本或导出数据的方式降到 2008或者干脆换高版本开发环境但要注意.csproj用 VS2010 打开高版本 SDK 的项目有兼容问题建议保持开发环境一致。5.2 运行报错找不到数据库连接字符串指向错误实例现象编译通过运行登录窗体点登录就抛 SqlException说无法打开数据库winyiyuan或无法登录。原因Data Source.\SQLEXPRESS指向的默认实例跟你实际装的实例名不一致。SQL Server 2008 安装时可以取实例名默认实例写.就行命名实例必须写全机器名加实例名。解决在 SSMS 登录框左侧的服务器名称下拉里看当前连接的服务器名把App.config的Data Source改成和它一致。改完重新生成项目同时把bin\Debug下的form.exe.config删掉让它重新生成避免读到旧配置。5.3 admin 登录失败大小写与表数据被改过现象输入管理员账号 admin、密码 admin提示用户名或密码错误。明明账号没错却进不去。原因常见三类。一是 SQL Server 排序规则区分大小写Admin和admin被视为不同字符串二是之前有人用默认密码登录后改过 yonghu 表里的密码三是登录代码里密码字段做了加密存储但界面上输入的明文没加密比对失败。解决先在 SSMS 里查SELECT username, password FROM yonghu直接看库里存的密码是什么形态。如果看到的是乱码或 hash 字符串说明登录代码里有 MD5 或 SHA 处理你就不能用明文 admin 去登需要用源码里的加密逻辑生成密文再 update 进去。如果库里就是明文 admin那检查一下 5.1 里说的排序规则用ALTER DATABASE winyiyuan COLLATE Chinese_PRC_CI_AS改成不区分大小写。5.4 编译报错 CS0241 与 Designer.cs 重复定义现象打开项目后按 F6 编译报 CS0241 或类型 X 已包含 Y 的定义指到某个.Designer.cs文件。原因最常见的是之前某人手动改过 Designer.cs把某个控件的声明重复写了一份或者把设计器生成的InitializeComponent()里的代码复制粘贴了。另一个可能是你的 VS 版本比 2010 新打开旧项目时设计器自动迁移出错产生了重复成员。解决用 Git 或备份还原这个 Designer.cs 原文件。没有备份的右键报错控件在属性窗口查看(Name)属性把重复的声明和赋值代码删掉一份。如果你只是上课交作业初始设计器代码能编译通过就不要动它改布局尽量用设计器视图拖拽改坏了CtrlZ不一定救得回来这是最大的后悔药。5.5 中文乱码与编码问题VS2010 默认编码的坑现象窗体上的中文按钮文字、MessageBox 提示在运行时显示成乱码或者源码里的中文注释变成锟斤拷。原因VS2010 的.cs文件默认是代码页 936GBK而 SQL Server 里的表数据如果是nvarchar存的中文在连接字符串没有指定Character Set时也可能显示异常。更常见的是你用了新版本 VS2019/2022打开旧项目自动转成了 UTF-8 但保存格式不一致。解决在 VS2010 里点击文件 → 高级保存选项把编码设为简体中文GB2312- 代码页 936再重新保存所有 .cs 文件。数据库连接字符串可以加上;Character Setutf8不适应于 SqlClient正确方式是确认 SQL Server 字段类型用nvarchar并且连接属性里Language正确一般默认即可。少用中文做表名和字段名这套系统里字段名是拼音比中文安全得多。6. 进阶技巧把费用批量结算改成带事务的原子操作这套系统每个操作基本是单条 SQL 直写但实际住院结算往往是一次操作要同时更新费用表、治疗状态和病人余额。我把它的事务版写法给你当作你改造这个项目的第一步练手。private void btnSettle_Click(object sender, EventArgs e) { using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); SqlTransaction tran conn.BeginTransaction(); try { // 1. 写费用记录 string sql1 INSERT INTO feiyong(patientId, totalFee, payTime) VALUES(pid, total, GETDATE()); SqlCommand cmd1 new SqlCommand(sql1, conn, tran); cmd1.Parameters.AddWithValue(pid, currentPatientId); cmd1.Parameters.AddWithValue(total, currentTotal); cmd1.ExecuteNonQuery(); // 2. 更新住院状态为已结算 string sql2 UPDATE zhuyuan SET status已结算 WHERE patientIdpid; SqlCommand cmd2 new SqlCommand(sql2, conn, tran); cmd2.Parameters.AddWithValue(pid, currentPatientId); cmd2.ExecuteNonQuery(); // 3. 扣减病人预缴余额 string sql3 UPDATE yonghu SET balance balance - total WHERE patientIdpid; SqlCommand cmd3 new SqlCommand(sql3, conn, tran); cmd3.Parameters.AddWithValue(total, currentTotal); cmd3.Parameters.AddWithValue(pid, currentPatientId); cmd3.ExecuteNonQuery(); tran.Commit(); MessageBox.Show(结算成功); } catch { tran.Rollback(); throw; } } }关键点在于BeginTransaction()之后这条连接上的所有操作都要显式传入这个事务对象new SqlCommand(sql, conn, tran)的第三个参数就是它漏掉任何一个命令那个命令会默认用隐式事务自动提交整个原子性就破了。GETDATE()是 SQL Server 端取当前时间避免各机器时间不一致。验证方法很直接——在tran.Commit()前手动抛一个异常看Rollback()是否把前两条已经执行的数据回滚掉。我从那以后每次改这种多表更新的业务逻辑都强制先写事务版再做界面这套源码给了我足够的训练场希望你也能把它用起来希望帮到你。本文还有配套的精品资源点击获取