
简介面向C#开发者的条码打印示例工程覆盖条码库集成、图形绘制、文档格式化、打印机通信、界面设计等关键环节适用于物流、仓储、零售等需要批量打印标签的业务场景。资源包共630个文件大小24.15MB以dll动态库、xml配置、cs源码、png示例图片和txt说明文档为主另有nupkg依赖包、sln工程文件等目录结构完整可直接在Visual Studio中加载运行。已有681人学习下载适合需要快速实现条码标签打印的C#开发者参考。通过该示例可掌握ZPL/EPL命令构造、GDI条码绘制、串口通信、WPF/WinForms界面搭建以及异常处理与日志记录等实用技能覆盖从条码生成到标签输出的完整流程。 搞C#上位机或者桌面工具开发的朋友大概率都接过这种需求做一个条码标签打印的小程序。这东西听起来简单不就是调打印机打个码吗但真正上手你会发现从条码生成到精确排版再到打印机驱动适配每一步都藏着坑。这篇文章就结合我自己做过的一个C#打印条码标签Demo把整个思路、代码、踩坑记录都摊开来讲希望能帮你少走点弯路。这个Demo本身解决的是“批量生成并打印条码标签”这个高频需求比如商品价签、固定资产标签、快递面单、货架标识甚至现在很火的抖音核销票底层逻辑都是这一套。适合刚接触C#打印开发的人参考也适合想优化现有打印逻辑的老手看看有没有遗漏的点。1. 打印方案选型为什么我选了C# WinForm PrintDocument1.1 标签打印到底在打什么大部分场景下的“条码标签打印”本质上不是图片打印而是“数据 模板”的组合输出。你需要把数据库里的编码、名称、价格等字段按固定版式渲染到一张小标签纸上并且保证条码能被扫码枪准确识别。这个场景有几个特点单张标签面积小、信息密度高、条码清晰度要求苛刻、批量打印时还要快。所以选型时就要考虑用什么技术生成条码用什么方式输出到打印机怎么控制打印位置不跑偏1.2 主流方案的横向对比我实际用过的方案主要有这么几种方案优点缺点适用场景C# WinForm PrintDocument原生支持不依赖第三方控件部署简单排版代码稍繁琐大多数中小型桌面工具调用打印机厂商SDK如得力、佳博、TSC指令控制精准速度快绑定品牌换打印机就要改代码生产环境固定机型Web页面 打印控件界面灵活适合网页端需要安装浏览器插件兼容性问题多B/S架构的系统SharpBarcode / ZXing.Net 报表控件条码生成能力强报表排版美观引入额外依赖学习成本略高复杂模板、批量套打我这个Demo选的是第一种C# WinForm PrintDocument ZXing.Net。原因是它最通用不绑硬件换打印机也能用代码可控性强每一毫米都能调而且ZXing.Net是开源库生成条码的稳定性和识别率都有保障。注意如果你的需求是高速批量打印比如一分钟三四百张那最好用厂商SDK的指令模式如TSPL、ZPL这个Demo更偏“通用性”和“学习参考”两者侧重点不同。2. 条码生成的核心细节与原理2.1 用ZXing.Net生成条码背后的逻辑ZXing.Net是Java版ZXing的C#移植版支持Code 128、EAN-13、QR Code等主流码制。在我的Demo里选的是Code 128原因是它自校验能力强、字符集全、密度高在标签打印场景里最常用。生成条码的核心代码不复杂但有几个参数直接影响打印出来的识别率using ZXing; using ZXing.Common; using ZXing.Rendering; public static Bitmap GenerateBarcode(string content, int width, int height) { var writer new BarcodeWriterBitmap { Format BarcodeFormat.CODE_128, Renderer new BitmapRenderer(), Options new EncodingOptions { Width width, Height height, Margin 1, // 条码左右留白单位是模块宽度 PureBarcode false // 设为false会附带可读文本 } }; return writer.Write(content); }这里有个新手容易忽略的点Margin不能设为0。条码左右两侧必须保留“静区”Quiet Zone扫码枪才能正确判断条码的起始和结束。静区太窄是扫码不灵敏甚至扫不出来的常见原因。2.2 打印DPI与尺寸换算的数学关系生成条码只是第一步打印的时候还要面对一个经典问题屏幕上的一像素和打印机的一个点完全是两回事。打印机分辨率常用DPI每英寸点数表示常见的是300DPI和600DPI。也就是说一英寸长度上打印机可以输出300个点。如果你的标签宽度是50mm换算成英寸约1.969英寸那么在300DPI的打印机上标签点阵宽度就是50mm 50 / 25.4 ≈ 1.9685 inch 1.9685 × 300 ≈ 590 点所以在写PrintDocument的打印逻辑时页面的尺寸设置非常关键。很多人的标签打出来偏了、裁切了就是因为直接用毫米数值传给打印机而没有考虑DPI转换。正确的做法是用PageSettings.PaperSize和Graphics.PageUnit来统一度量单位让GDI帮我们换算。在Demo里我是这么处理的printDocument.DefaultPageSettings.PaperSize new PaperSize(CustomLabel, 590, 354); printDocument.DefaultPageSettings.Margins new Margins(10, 10, 10, 10);这里的590和354就是50mm宽、30mm高在300DPI下的像素值。通过这种方式后续绘制时就可以直接用像素坐标去精确控制条码和文字的位置。3. 实操过程构建一个功能完整的打印Demo3.1 项目结构与界面设计做这个Demo时我把它分成了三个核心模块UI输入层、标签渲染类、打印控制类。这样做的好处是后续接数据库、改模板只需要替换输入层就行渲染和打印逻辑完全不用动。UI部分很简单主要就几个控件TextBox输入条码内容、名称、价格NumericUpDown设置打印份数ComboBox选择打印机Button触发打印预览和直接打印界面设计走实用路线不需要花哨能清晰表达逻辑就行。核心是把“单张标签预览”做出来让用户打印前能直观看到效果。3.2 标签渲染把数据画到纸张上核心的打印工作是在PrintPage事件里完成的。这个事件的触发机制是每次要输出一页时就会触发一次所以我们在这个事件里画一次标签就完成一张标签的渲染。private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e) { Graphics g e.Graphics; // 设置高质量输出 g.SmoothingMode System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.InterpolationMode System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; // 绘制内容区域边框可选 RectangleF contentRect new RectangleF(10, 10, 570, 334); g.DrawRectangle(Pens.Transparent, contentRect.X, contentRect.Y, contentRect.Width, contentRect.Height); // 生成条码并绘制到指定位置 using (Bitmap barcode BarcodeService.GenerateBarcode(txtCode.Text.Trim(), 300, 80)) { g.DrawImage(barcode, new RectangleF(30, 20, 300, 80)); } // 绘制商品名称 g.DrawString(txtName.Text.Trim(), new Font(微软雅黑, 9, FontStyle.Regular), Brushes.Black, new RectangleF(30, 110, 300, 40)); // 绘制价格 g.DrawString( txtPrice.Text.Trim(), new Font(Arial, 14, FontStyle.Bold), Brushes.Black, new RectangleF(30, 160, 150, 30)); // 判断是否还有下一页 e.HasMorePages false; }这里每个人都可以根据自己的标签版式调整坐标。我自己比较喜欢用固定的x、y坐标去计算而不是用FlowLayoutPanel那一套因为在打印这种固定模板的场景里绝对定位更可控、更不易发生偏移。3.3 批量打印的完整闭环批量打印是实际需求里最常被问到的点。比如要打印一百份价格标签不可能一个一个点。我在这里用了一个简单的计数器来实现private int _currentPrintIndex 0; private ListLabelData _printList new ListLabelData(); private void btnBatchPrint_Click(object sender, EventArgs e) { // 模拟数据源这里实际应用中可能来自数据库或Excel _printList.Clear(); for (int i 1; i numericUpDown1.Value; i) { _printList.Add(new LabelData { Code txtCode.Text.Trim() i.ToString(D4), Name txtName.Text.Trim(), Price txtPrice.Text.Trim() }); } _currentPrintIndex 0; printDocument.Print(); } private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e) { if (_currentPrintIndex _printList.Count) { e.HasMorePages false; return; } RenderLabel(_printList[_currentPrintIndex], e.Graphics); _currentPrintIndex; // 还有就继续打印下一页 e.HasMorePages _currentPrintIndex _printList.Count; }这样做的好处是理解简单而且PrintDocument天然支持这种一页一页连续给的任务队列模式不需要额外开线程。提示数据量大时比如几百张注意不要在PrintPage事件里去查数据库因为每打一张都会触发一次频繁连接数据库会很慢。建议一次性加载到内存中再逐条渲染。3.4 打印预览别等打印了才发现问题我强烈建议每个打印工具都加上预览功能。PrintDocument本身有PrintPreviewControl或PrintPreviewDialog用起来很简单效果也很直观private void btnPreview_Click(object sender, EventArgs e) { PrintPreviewDialog previewDialog new PrintPreviewDialog(); previewDialog.Document printDocument; previewDialog.ShowDialog(); }这能帮你省下大量的标签纸和墨。因为标签纸虽然不贵但每次调试打印都切一下标签时间成本是真的高。4. 常见问题与排查技巧实录4.1 打印内容偏移或裁切这个问题在这类开发里出现的频率最高。排查思路分三步走第一步确认页面纸张尺寸和实际标签纸尺寸是否一致。比如你买的是50×30mm的标签纸但打印机驱动程序里默认页面是A4那必然裁切。解决办法是在驱动设置里自定义纸张或者在代码里显式设置PaperSize。第二步确认边距设置。很多打印机不能无边距打印必须留出上下左右至少几个毫米的边距。代码里设置Margins(0,0,0,0)在部分打印机上会强制扩大边距内容反而偏移了。我一般设成5~10个像素的边距实测更稳妥。第三步用打印机的“打印测试页”功能来校准。先在驱动里打一张测试页看打印机是否按预期输出再回过来调代码。这样可以快速区分是代码问题还是打印机纸张配置问题。4.2 条码扫码枪扫不出来这个问题的排查顺序先看条码内容本身是否合规Code 128对字符有范围限制再看生成的条码图片是否过小或过大最后重点检查静区。代码里生成条码的Margin参数我前面强调了必须保留。另一个坑是如果条码内容中间包含中文Code 128是编不进去的会出现空白或乱码。中文内容要么转成拼音、要么用QR Code格式这个要根据实际场景取舍。还有一点容易忽视的是条码的对比度。标签打印机上如果碳带质量不行或者打印浓度偏低条码的着色会不实扫码枪扫描时会因为反射率不足而失败。这个时候在打印机设置里调高打印浓度、降低打印速度通常能有效改善。4.3 新打印机接入后完全乱套不同品牌打印机对页面尺寸、边距的容错差异很大。有的打印机即使你代码里设了50mm宽它也能按这个裁切有的打印机则会“根据内容实际占用自动扩展”导致条码出现在纸边缘。我的做法是不依赖单一打印机的默认纸张设置在打印代码里强制使用自定义PaperSize并在界面初始化时加载系统打印机列表让用户手动选择目标打印机。foreach (string printer in PrinterSettings.InstalledPrinters) { cmbPrinter.Items.Add(printer); } printDocument.PrinterSettings.PrinterName cmbPrinter.SelectedItem.ToString();另外每换一台打印机最好先打印一次单张测试页确认效果再跑批量。批量打印中途出错会浪费大量标签纸这个问题一定得前置规避。4.4 WinForm打印进程挂起或卡死如果打印命令发出去后程序界面假死多半是因为打印队列阻塞。尤其是网络打印机队列里有残留任务时会卡很长时间。排查时先看打印队列清掉所有任务再试如果是长时间大批量打印可以考虑用BackgroundWorker把打印丢到后台线程避免阻塞UI线程。还有个常见鸡肋场景打印机驱动异常导致“无法创建打印作业”这种问题通常重启电脑或重装打印机驱动就能解决和代码关系不大。4.5 打印速度慢怎么办标签打印速度慢主要瓶颈往往是条码生成过程。每张标签都重新生成一张Bitmap然后绘制、释放这种操作循环几百次效率确实偏低。优化思路有两个方向一是如果条码内容只是编号递增可以预先生成一个包含所有条码的图片列表打印时直接拿来用二是在批量打印同一尺寸、相同模板的情况下可以在循环里复用Font、Pen、Brush这些GDI对象避免反复创建销毁带来的性能损耗。代码里的using块虽然语法干净但是在性能敏感的循环里不适合高频创建对象。5. 打印模板扩展与换纸适配的教训5.1 一机多纸型怎么办在实际使用中经常遇到这种情况同一台电脑上午打50×30的价格签下午要打100×70的箱唛标签。如果模板是硬编码坐标就只能改代码。我这版Demo虽然主体是固定的但设计时留了一个“模板配置文件”的思路把标签的宽度、高度、各字段位置、字号全部存在一个XML或者JSON文件里打印前读取。这样换纸型只需要改配置文件不需要重新编译。例如模板配置的核心就是一组坐标值{ LabelWidthMM: 50, LabelHeightMM: 30, Fields: [ { Name: Barcode, X: 30, Y: 20, W: 300, H: 80 }, { Name: Name, X: 30, Y: 110, W: 300, H: 40 }, { Name: Price, X: 30, Y: 160, W: 150, H: 30 } ] }这样代码只负责“读取配置 → 画内容”换纸型时直接改配置省心很多。如果你打算长期用这个Demo我建议把这个扩展机制加上。5.2 打印内容被缩放失真有时候用户反馈条码打印出来模糊看代码往往发现是用了Graphics.ScaleTransform或者把位图强行拉伸导致的。条码这类对清晰度要求高的内容尽量用1:1比例输出。如果标签太小放不下那就应该减小条码尺寸而不是拉伸图片。用BitmapRenderer生成条码时Width和Height直接控制像素数。比如300×80的位图在300DPI下实际尺寸是25.4×6.77mm。如果标签宽度不够缩小这个尺寸就行不要靠DrawImage时的Rect参数去拉伸。5.3 批量数据来源的思考Demo里我直接用UI控件输入数据但真实生产环境里数据可能来自Excel、SQL Server、MySQL甚至MES系统接口。这里给一个更通用的设计思路核心打印方法只接收一个ListLabelData作为数据源UI层负责从各个渠道收集数据打印层只关心渲染输出。这样设计之后从Excel导入条码数据或者从数据库拉取商品信息本质上只是“填_dataList”这一步的差异打印逻辑稳定不变。后续你如果需要支持扫码枪逐条输入、自动递增流水号也都是在数据准备阶段做文章打印层完全不用动。6. 给新手的最后几点建议如果今天你就是想要一个能跑起来、能打印的C#条码标签Demo按上面的代码顺序组织好项目基本就能用了。但如果你想把这件事做得更深入还有几个方向值得探究也顺便分享一点我自己的体会。打印机兼容性始终是最大的变量。代码写得再漂亮到了不配合的打印机驱动上照样能给你整出偏移、缩放、跳纸的问题。所以做这类工具手边要常备一个“单张测试打印”按钮任何参数调整之后先单张确认再批量跑这个习惯能帮你省掉非常多麻烦。条码内容的规范要前置控制。我曾经接手过一个项目用户把Excel里的产品描述直接当成条码内容来打印结果Code 128识别率极低扫码枪反映时好时坏。最后排查发现是内容里混入了特殊字符。后来我在打印前加了数据校验规则不符合规范的直接弹窗提示类似“条码内容包含非法字符”这样的明确报错。数据规范这件事永远比打印渲染本身更值得花心思。打印是一门“看起来简单、做起来多坑”的活。同样的代码换一台打印机、换一卷纸、换一种碳带效果都可能不同。所以调试这类项目时多做记录——哪台打印机、什么纸型、哪个版本的驱动、用什么参数能完美输出这些一手资料非常宝贵。我现在自己做打印项目就会顺手维护这份参数档案长远看能省下大量重复试错的时间。C#打印条码标签这块的技术本身并不炫酷但它是无数个管理系统、ERP、门店运营工具中不可或缺的一环。希望这篇博客里的经验和踩坑记录能帮你顺顺利利地把标签打出来而不是在打印机前折腾半天“明明看着没问题打出来就是不对”。本文还有配套的精品资源点击获取