简介:这份资源是一套基于 C# WinForms 开发的快递单打印系统完整源码,面向具备一定 C# 基础、希望学习桌面应用与打印技术的开发者,可用于快速搭建或参考快递单模板设置、打印、查询及管理员权限管理等典型业务场景。压缩包共 97 个文件,约 2.73MB,以 34 个 cs 源码文件为核心,配合 14 个 resx 与 14 个 ico 资源、16 个 gif 及 bmp、jpg 图片素材,另有 csproj、sln 工程文件与 mdf、ldf 数据库文件,覆盖界面设计、数据访问与打印模块。目前已有 753 人学习下载。读者可从中获取模板定制与数据绑定的实现思路、基于 PrintDocument 的打印与预览写法、ADO.NET 查询展示方案以及 RBAC 权限控制示例,目录按 DAL、UI、Common 等分层组织,便于对照理解 WinForms 项目的整体结构与排错方法。
1. 从一张手写快递单说起:这套系统到底要解决什么
电商小团队最怕的不是订单多,而是订单多到要手抄快递单。地址、电话、收件人、商品明细,一张单子抄错一个数字,包裹就可能绕城一圈再退回来。C# WinForms 快递单打印系统要解决的正是这个场景:把订单数据录入、面单模板排版、批量打印、历史查询串成一条线,让一张快递单从录入到出纸不超过十秒。它适合两类人——正在做 C# 课程设计或毕设、需要一个完整桌面项目撑起答辩的学生,以及小仓库、工作室里确实要打单、又不想上重型 ERP 的从业者。整套方案不依赖数据库服务器,单机就能跑,技术栈就是 WinForms + ADO.NET + 本地数据库,门槛低但结构完整。
2. 需求拆解与总体设计:四个模块撑起一条打单流水线
2.1 先把业务流程画清楚,再谈界面
做桌面项目最容易翻车的地方,是一上来就拖控件。我一般会先把业务流写成一句话:录单 → 选模板 → 预览 → 打印 → 存档。这五步对应四个功能模块,界面只是它们的皮。
- 订单录入模块:收件人、电话、地址、寄件人、商品名称、数量、备注,字段校验在保存前完成。
- 模板管理模块:面单的纸张尺寸、字段坐标、字体字号、条码位置,全部可配置,不写死在代码里。
- 打印控制模块:单张打印、批量打印、打印预览,负责把数据填进模板并送到打印机。
- 历史查询模块:按时间、收件人、单号检索,支持重打和导出。
这样拆的好处是答辩时能讲清楚「每个模块负责什么、模块之间怎么通信」。模块之间用事件和公共数据层解耦,录入模块只管存数据,打印模块只管取数据渲染,谁都不越界。
2.2 技术选型:为什么是 WinForms 而不是别的
很多人会问,现在都讲 Web 和跨平台,为什么还选 WinForms。答案很实际:快递单打印强依赖 Windows 打印驱动和System.Drawing.Printing命名空间,WinForms 对打印机的控制最直接,PrintDocument、PrintPreviewDialog、PageSetupDialog都是现成的。换别的框架,光是对接打印机和毫米级坐标就要多花一倍时间。
数据层用 SQLite 或 Access 都行,课程设计里 Access 更省事,从业场景我倾向 SQLite,单文件、免安装、备份就是拷一个文件。数据访问统一走 ADO.NET,参数化查询防注入,别用字符串拼接 SQL,这是血泪经验。
| 层次 | 技术选型 | 职责 |
|---|---|---|
| 表现层 | WinForms 窗体与控件 | 录入、预览、操作入口 |
| 业务层 | C# 类库(OrderService 等) | 校验、模板解析、打印调度 |
| 数据层 | ADO.NET + SQLite/Access | 订单与模板的持久化 |
| 打印层 | System.Drawing.Printing | 坐标绘制、分页、出纸 |
2.3 数据库表设计:三张表就够用
不要一上来设计十几张表,课程设计和轻量交付场景,三张核心表足够撑起全部功能。
-- 订单主表:一条记录对应一张快递单 CREATE TABLE Orders ( OrderId INTEGER PRIMARY KEY AUTOINCREMENT, OrderNo TEXT NOT NULL UNIQUE, -- 内部单号,打印时可用 Receiver TEXT NOT NULL, -- 收件人 Phone TEXT NOT NULL, -- 电话 Address TEXT NOT NULL, -- 收件地址 Sender TEXT, -- 寄件人 Goods TEXT, -- 商品名称 Quantity INTEGER DEFAULT 1, -- 数量 Remark TEXT, -- 备注 CreatedAt TEXT NOT NULL -- 创建时间,ISO 格式 ); -- 模板表:一套坐标参数对应一种面单 CREATE TABLE Templates ( TemplateId INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, -- 模板名,如"标准面单100x150" PaperWidth INTEGER NOT NULL, -- 纸张宽,单位毫米 PaperHeight INTEGER NOT NULL, -- 纸张高,单位毫米 LayoutJson TEXT NOT NULL -- 字段坐标配置,JSON 存储 ); -- 打印日志表:重打和追溯的依据 CREATE TABLE PrintLogs ( LogId INTEGER PRIMARY KEY AUTOINCREMENT, OrderId INTEGER NOT NULL, TemplateId INTEGER NOT NULL, PrintedAt TEXT NOT NULL, FOREIGN KEY (OrderId) REFERENCES Orders(OrderId) );LayoutJson是关键设计:把每个字段的 X/Y 坐标、字号、对齐方式存成 JSON,换一种面单只改配置不改代码。字段坐标用毫米存,打印时再换算成像素,避免不同 DPI 下位置漂移。
2.4 项目目录结构:让答辩老师一眼看懂分层
ExpressPrint/ ├── Forms/ # 窗体:MainForm、OrderEditForm、TemplateForm ├── Models/ # 实体类:Order、Template、PrintLog ├── Services/ # 业务:OrderService、PrintService、TemplateService ├── Data/ # 数据访问:DbHelper、各 Repository ├── Resources/ # 图标、默认模板 └── Program.cs # 入口目录即架构,Forms不直接写 SQL,Services不碰控件,Data只负责增删改查。这条纪律守住了,后期加功能才不会牵一发动全身。
3. 核心模块实现:从录入校验到面单出纸
3.1 订单录入与字段校验
录入窗体上放TextBox、ComboBox、DateTimePicker,保存按钮里先校验再入库。电话用正则卡住,地址不能为空,数量必须是正整数。校验失败用ErrorProvider在控件旁标红,比弹一堆 MessageBox 友好。
private bool ValidateInput() { bool ok = true; // 收件人必填 if (string.IsNullOrWhiteSpace(txtReceiver.Text)) { errorProvider1.SetError(txtReceiver, "收件人不能为空"); ok = false; } // 电话:11 位手机号或带区号座机 if (!Regex.IsMatch(txtPhone.Text.Trim(), @"^1[3-9]\d{9}$|^0\d{2,3}-?\d{7,8}$")) { errorProvider1.SetError(txtPhone, "电话格式不正确"); ok = false; } // 数量必须为正整数 if (!int.TryParse(txtQuantity.Text, out int qty) || qty <= 0) { errorProvider1.SetError(txtQuantity, "数量必须为正整数"); ok = false; } return ok; }校验通过后调用OrderService.Save(order),内部用参数化 SQL 写入。注意ErrorProvider在下次校验前要Clear(),否则旧的错误标记会残留,这是新手常踩的坑。
3.2 模板配置:把坐标从代码里赶出去
模板配置界面让用户输入纸张宽高和每个字段的坐标,保存成 JSON。渲染时反序列化成对象,按毫米转像素绘制。换算公式是像素 = 毫米 / 25.4 * DPI,DPI 从打印机设置里取,默认 96 或 203(热敏机常见)。
public class FieldLayout { public string FieldName { get; set; } // 对应 Order 的属性名 public float XMm { get; set; } // 距左边距,毫米 public float YMm { get; set; } // 距上边距,毫米 public float FontSize { get; set; } // 字号,磅 public bool Bold { get; set; } } // 毫米转像素,dpi 由打印机决定 private float MmToPixel(float mm, float dpi) => mm / 25.4f * dpi;字段名和实体属性对齐,渲染时用反射取值,新增字段不用改打印代码。反射有性能开销,但一张单子十几个字段,完全无感。
3.3 打印控制:PrintDocument 的正确用法
打印的核心是PrintDocument的PrintPage事件,在里面按模板坐标逐字段DrawString。批量打印时把订单列表塞进队列,每打完一张触发HasMorePages判断是否继续。
private void OnPrintPage(object sender, PrintPageEventArgs e) { var order = _printQueue[_currentIndex]; var layout = _template.Layout; float dpi = e.Graphics.DpiX; foreach (var field in layout.Fields) { // 反射取订单字段值 var prop = typeof(Order).GetProperty(field.FieldName); string text = prop?.GetValue(order)?.ToString() ?? ""; using var font = new Font("宋体", field.FontSize, field.Bold ? FontStyle.Bold : FontStyle.Regular); float x = MmToPixel(field.XMm, dpi); float y = MmToPixel(field.YMm, dpi); e.Graphics.DrawString(text, font, Brushes.Black, x, y); } _currentIndex++; e.HasMorePages = _currentIndex < _printQueue.Count; // 还有单子就继续 }HasMorePages是批量打印的开关,忘了设它,要么只打一张,要么无限循环。打印前务必调PrintPreviewDialog让用户确认,预览和实际出纸用同一个PrintDocument实例,保证所见即所得。
3.4 历史查询与重打
查询用DataGridView绑定DataTable,条件拼在WHERE里但值走参数。重打时从PrintLogs找到原模板,重新走一遍打印流程,并在日志里追加一条记录。导出用StreamWriter写 CSV,注意中文编码用Encoding.UTF8带 BOM,否则 Excel 打开乱码。
4. 避坑与排查:打印项目最容易翻车的五个点
4.1 预览正常、出纸错位
现象:预览里字段位置对,实际打印整体偏移几毫米。原因多半是打印机有不可打印边距,或者代码里用了固定像素坐标没考虑 DPI。解决:坐标统一用毫米存,绘制时按e.Graphics.DpiX换算,并在模板里预留 3~5 毫米安全边距。
4.2 中文变问号或方块
现象:打印出来中文全是乱码。原因是字体没指定中文字体,或者用了Graphics.DrawString默认字体。解决:显式指定「宋体」「微软雅黑」等中文字体,别依赖系统默认;CSV 导出加 UTF-8 BOM。
4.3 批量打印卡死界面
现象:点批量打印后窗体无响应。原因是打印循环跑在 UI 线程。解决:用BackgroundWorker或async/await把打印放到后台,进度用ProgressBar回报,打印期间禁用按钮防重复点击。
4.4 数据库被占用打不开
现象:程序第二次启动报「文件已被占用」。原因是 SQLite 连接没关,或者用了文件锁模式。解决:using包裹连接和命令对象,确保释放;单机场景连接字符串加Pooling=False减少锁竞争。
4.5 模板 JSON 反序列化失败
现象:换模板后程序崩溃,提示 JSON 解析错误。原因是手工改了配置文件、字段名拼错或类型不匹配。解决:反序列化包try/catch,失败时回退到默认模板并提示用户;模板保存前做一次序列化往返校验。
5. 验收与答辩:怎么证明这套系统真的能用
验收不是看界面漂不漂亮,而是看它能不能稳定出纸。我一般按三步走。第一步,功能验收:准备 20 条测试订单,覆盖正常单、超长地址、特殊字符电话,逐条录入并打印,检查字段是否完整、位置是否准确。第二步,压力验收:一次性批量打印 50 张,观察是否卡顿、是否漏打、日志是否完整记录。第三步,异常验收:故意拔掉打印机、填错电话、改坏模板 JSON,看程序是崩溃还是给出可读提示。
答辩时老师最爱问三个问题:数据怎么存的、打印坐标怎么算的、批量打印怎么不卡。提前把这三条讲成一句话答案——参数化 SQL 存 SQLite、毫米坐标按 DPI 换算、后台线程加进度条——基本就稳了。下面这张验收清单可以直接照着打勾。
| 验收项 | 通过标准 | 检查方式 |
|---|---|---|
| 录入校验 | 空值、错电话被拦截 | 手工输入异常数据 |
| 模板切换 | 换模板后坐标正确 | 两套模板各打一张 |
| 批量打印 | 50 张无漏打、界面可响应 | 批量任务加计时 |
| 历史重打 | 能按单号找回并重打 | 查询后点重打 |
| 异常提示 | 打印机离线有明确提示 | 断开打印机测试 |
有个细节值得单独说:答辩演示前,一定用目标打印机实打一张,别只放预览。我见过太多预览完美、现场出纸偏移的场面。从那以后我每次交付打印类项目,都强制走一遍「真机出纸 + 批量 50 张 + 拔打印机」三连测,宁可自己先翻车,也不在验收现场翻车。希望这套结构和清单能帮到你,照着改改就能变成你自己的完整方案。
本文还有配套的精品资源,点击获取