简介:这份资源面向仍在使用 Delphi 6 的开发者,提供一套无需任何外部插件即可生成一维条形码的控件源码,适用于库存管理、产品标识、标签打印等需要条码输出的场景。压缩包共 17 个文件,约 239KB,以 pas 源码、dcu 编译单元、dfm 窗体、dpr 工程文件为主,另含 bmp 位图、res 资源、htm 说明及可执行示例,便于直接编译运行与二次开发。控件支持 Code 39 与 Code 128 等常见一维码制,涵盖字符编码、条纹绘制、校验码计算与打印输出等环节,读者可从中理解条码编码原理、Delphi 图形 API 绘制方式以及打印布局处理思路。目前已有 1272 人学习下载,适合希望低成本集成条码功能或研究 VCL 绘图与打印实现的中级开发者参考。
1. 拆开这个 Delphi 一维条码控件:为什么老项目还在用它
上周帮一个做仓储管理系统的朋友排查打印问题,他们那套 Delphi 6 写的老系统跑了十几年,条码打印模块突然在几台新装的 Windows 上打不出 Code128,换了几款第三方控件要么收费、要么依赖运行库、要么在 XP 上直接崩。最后翻出一个压箱底的.rar——一个纯 Delphi 代码实现的一维条形码控件,没有 DLL、没有 OCX、没有外部依赖,把单元文件拖进工程就能用。这件事让我意识到,在工控、仓储、零售这些领域,Delphi 6/7 的存量项目远比想象中多,而条码打印恰恰是这些系统里最不能出问题的一环。
这份资源解决的就是这个场景:不装插件、不注册组件、不依赖任何第三方运行库,用 Object Pascal 直接画出 Code39 和 Code128 两种最常用的一维条码。适合谁?维护老 Delphi 项目的工程师、需要给现有系统快速加条码打印功能的开发者,以及那些被商业控件授权费卡住的小团队。下面我从代码结构、编码原理、实际调用到踩坑记录,把这份东西拆开讲清楚。
2. 控件结构与编码原理:先搞懂它在画什么
2.1 单元文件里到底有什么
解压后核心通常是一到两个.pas单元文件,命名类似Barcode.pas或Code128.pas。打开看,结构一般分三层:最底层是编码表常量数组,中间层是编码转换函数,最上层是绘制过程。编码表是硬编码的——Code39 的 43 个字符加起始/终止符,Code128 的 107 个码字,全部以二进制位串或整数数组形式写死在const段里。这意味着它不查表文件、不读配置,编译进去就是死的,换台机器行为完全一致。
绘制层通常提供两种输出方式:一是直接画到TCanvas上,适合TPrinter打印或TImage预览;二是生成位图返回TBitmap,方便嵌入报表或保存成文件。没有用 GDI+ 或任何图形库,就是最原始的Canvas.MoveTo/Canvas.LineTo画竖条。这种做法的好处是兼容性极强,从 Delphi 6 到 Delphi 10 都能编译;代价是没有抗锯齿,放大后边缘是硬的——但条码打印恰恰不需要抗锯齿,反而要求边缘锐利。
2.2 Code39 和 Code128 的编码差异
Code39 是最简单的一维码之一,每个字符由 9 个元素组成(5 条 4 空),其中 3 个是宽元素。它的字符集只有 43 个:数字、大写字母、-、.、空格、$、/、+、%。编码逻辑直白——查表拿到 9 位模式,按位画宽窄条即可。起始符和终止符都是*,所以实际数据前后会自动加上*。校验位可选,Mod 43 算法,很多场景直接不校验。
Code128 复杂得多。它有三个子集:A 集含控制字符和大写字母,B 集含大小写字母和数字,C 集专门放数字对。每个码字 11 位,以11010010000(Start B)这类模式开头,末尾跟校验位和停止符1100011101011。Code128 的密度比 Code39 高约 30%,同样宽度能编更多数据,而且支持全 ASCII。代价是编码逻辑要处理子集切换——比如一串数字中间夹个字母,得从 C 集切到 B 集再切回来,切换码字本身也占位。
这份控件里,Code39 的实现通常不到 100 行,Code128 的编码函数会到 200 行以上,主要开销就在子集切换判断上。我见过一些简化版直接固定用 B 集,不切 C 集,数字多的时候条码会偏宽,但能扫——如果你的标签纸宽度够,这反而是最省事的做法。
2.3 条宽、窄宽比与静区参数
画条码不是把位串一比一画出来就完事。三个参数直接决定能不能扫出来:
| 参数 | 典型值 | 作用 | 调错后果 |
|---|---|---|---|
| 窄条宽度 | 1~3 像素 | 最细那条的宽度 | 太小打印糊,太大标签放不下 |
| 宽窄比 | 2.0~3.0 | Code39 宽条/窄条 | 小于 2 扫不出,大于 3 太占地方 |
| 静区 | 10×窄条宽 | 条码左右留白 | 不留白扫描枪对不上焦 |
控件里这些通常以属性形式暴露,比如NarrowWidth、WideRatio、QuietZone。默认值往往偏保守——窄条 2 像素、宽窄比 3、静区 10 倍。实际用的时候,如果打印机是 203 DPI 的热敏机,窄条设 2 像素对应约 0.25mm,刚好在多数扫描枪的识别范围内。设成 1 像素在屏幕上看着清楚,一打印就断线,这是最常见的翻车点。
3. 在 Delphi 6 里跑起来:从拖单元到出条码
3.1 把单元加入工程并调用绘制过程
假设解压得到Barcode.pas,里面有一个TBarcode类或一组独立函数。最省事的用法是直接调函数,不注册组件。在窗体单元的uses里加上Barcode,然后在按钮事件里写:
procedure TForm1.btnPrintClick(Sender: TObject); var BC: TBarcodeRenderer; begin BC := TBarcodeRenderer.Create; try // 设置编码类型:bcCode39 或 bcCode128 BC.Symbology := bcCode128; // 要编码的数据,注意 Code39 只支持大写 BC.Data := 'ABC-123456'; // 窄条宽度,单位像素 BC.NarrowWidth := 2; // 宽窄比,Code39 有效,Code128 忽略 BC.WideRatio := 3; // 左右静区,单位像素 BC.QuietZone := 20; // 直接画到 Printer 的画布上 Printer.BeginDoc; BC.DrawTo(Printer.Canvas, 100, 200, 400, 80); Printer.EndDoc; finally BC.Free; end; end;这段代码的逻辑很直白:创建渲染器、设参数、调DrawTo。DrawTo的五个参数依次是目标画布、左上角 X、左上角 Y、最大宽度、高度。注意宽度参数是"最大宽度"——控件会根据数据长度和窄条宽度算出实际需要多宽,如果算出来超过你给的值,有的版本会截断,有的会压缩,这点必须先看源码确认。我一般会先调BC.GetWidth拿到实际宽度,再决定打印位置。
3.2 参数怎么设才扫得出来
上面代码里的参数不是随便填的。NarrowWidth := 2对应 203 DPI 打印机约 0.25mm,这是多数激光和热敏打印机的安全值。如果你用的是 300 DPI 的机器,窄条设 2 像素就只有 0.17mm,偏细,建议设 3。QuietZone := 20是 10 倍窄条宽,刚好卡在下限,标签空间紧张时可以压到 15,再小就玄学了——同一批标签,有的扫得出有的扫不出,查半天发现是静区不够。
Code128 的WideRatio无效,因为它的每个码字都是固定 11 位,不存在宽窄条概念。但 Code39 必须设,而且不能设 2.0 以下。我试过 1.8,打印出来肉眼看着正常,扫描枪十次里只认三次。设 3.0 最稳,但条码会变宽约 50%,标签窄的话自己权衡。
还有一个隐藏参数是高度。DrawTo里的高度参数一般建议不低于 15mm,对应 203 DPI 约 120 像素。太矮的条码对扫描枪的角度要求很苛刻,收银台那种平扫的还好,仓库里用手持枪斜着扫的,矮条码就是灾难。
3.3 打印预览与位图导出
不是所有场景都直接打印。有时候要先在界面上预览,或者把条码嵌进 FastReport 之类的报表里。这时候用位图输出:
procedure TForm1.ShowPreview; var BC: TBarcodeRenderer; Bmp: TBitmap; begin BC := TBarcodeRenderer.Create; Bmp := TBitmap.Create; try BC.Symbology := bcCode39; BC.Data := 'TEST001'; BC.NarrowWidth := 2; BC.WideRatio := 3; BC.QuietZone := 20; // 先生成位图,宽高由控件计算 BC.DrawToBitmap(Bmp, 300, 80); // 显示在 Image 控件上 Image1.Picture.Assign(Bmp); // 也可以存成文件 Bmp.SaveToFile('barcode.bmp'); finally Bmp.Free; BC.Free; end; end;DrawToBitmap的宽高参数和DrawTo不同——这里是精确尺寸,控件会把条码缩放到这个范围内。如果给的宽度不够,条码会被压扁,窄条变细,可能扫不出。稳妥做法是先调BC.GetWidth(300)之类的函数拿到推荐宽度,再传给DrawToBitmap。存成 BMP 的好处是可以用在报表工具里,坏处是文件大、没压缩,批量生成几百个标签时磁盘占用要注意。
提示:Delphi 6 的
TBitmap默认是 24 位色,存 BMP 没问题,但如果要转成单色位图给某些老打印机用,得手动调PixelFormat或做二值化处理,否则打印出来灰度边缘会发虚。
4. 避坑与排查:那些让我加班到凌晨的细节
4.1 打印出来扫描枪不认,屏幕上却正常
现象:预览窗口里条码清清楚楚,打印到纸上用扫描枪怎么都扫不出。原因通常有两个——一是打印机分辨率与窄条宽度不匹配,屏幕上 1 像素对应打印机的多个点,实际窄条被"加粗"或"变细"了;二是打印机驱动做了缩放,比如把 A4 内容缩放到标签纸上。解决:把NarrowWidth按打印机 DPI 换算成物理尺寸,203 DPI 下 2 像素约 0.25mm,300 DPI 下要设 3;同时在打印驱动里关掉"缩放适应纸张",用 100% 原始尺寸。
4.2 Code39 数据里带小写字母,条码直接空白
现象:传入abc123,画出来一片空白或只有起始符。原因:Code39 标准字符集只有大写字母,控件遇到小写直接跳过或报错。解决:在赋值前统一转大写BC.Data := UpperCase(Edit1.Text)。如果业务上必须区分大小写,换 Code128 B 集,它支持全 ASCII。这个坑我踩过两次,第二次是因为忘了 Code39 连空格都支持但小写就是不认。
4.3 Code128 数字串太长,条码宽到标签外
现象:编 20 位纯数字,条码宽度超出标签纸,打印被截断。原因:控件默认用 B 集编码,每个数字占一个码字 11 位;而 C 集可以把两个数字压进一个码字,密度翻倍。解决:看控件是否支持自动切 C 集,如果不支持,手动在数据前加 C 集起始符,或者干脆换用 Code39——虽然密度低,但 20 位数字在 40mm 标签上 Code39 反而可能放得下,因为 Code39 没有子集切换开销。这是个反直觉的结论,实测过才知道。
4.4 在 Windows 10 上编译报错 "Canvas 不支持"
现象:Delphi 6 工程在 Win10 上编译通过,运行时画条码抛异常。原因:老版本 Delphi 的TPrinter.Canvas在某些新驱动下返回空或类型不匹配。解决:不要直接画到Printer.Canvas,先画到TBitmap,再用Printer.Canvas.Draw贴上去。多一步中转,兼容性立刻好转。这个技巧对所有老 Delphi 打印代码都适用,不止条码。
4.5 静区被"优化"掉了
现象:条码左右没有留白,紧贴着标签边缘,扫描枪要贴得很近才认。原因:调用DrawTo时给的 X 坐标就是静区起点,如果 X 设成 0,静区就被画到画布外了。解决:X 坐标至少留QuietZone的值,比如BC.DrawTo(Canvas, BC.QuietZone, 200, 400, 80)。或者干脆在标签设计时左右各留 3mm 空白,比在代码里算更省心。
5. 进阶:批量生成与校验位的手动补全
批量打印是仓储系统的刚需——一次生成几百个库位标签,每个条码数据不同。用这个控件做批量,关键是别在循环里反复创建销毁渲染器,把TBarcodeRenderer提到循环外,只改Data属性:
procedure TForm1.BatchPrint; var BC: TBarcodeRenderer; I: Integer; Bmp: TBitmap; begin BC := TBarcodeRenderer.Create; Bmp := TBitmap.Create; try BC.Symbology := bcCode128; BC.NarrowWidth := 2; BC.QuietZone := 20; Printer.BeginDoc; for I := 1 to 200 do begin BC.Data := Format('LOC%06d', [I]); // 每 4 个标签换一页,按实际标签纸排布调整 if (I - 1) mod 4 = 0 then begin if I > 1 then Printer.NewPage; end; BC.DrawTo(Printer.Canvas, 50, 100 + ((I - 1) mod 4) * 150, 400, 80); end; Printer.EndDoc; finally Bmp.Free; BC.Free; end; end;这段代码里Format('LOC%06d', [I])生成LOC000001到LOC000200,(I-1) mod 4控制每页 4 个标签的纵向排布。实际标签纸的间距要自己量——常见的是 40mm×30mm,一行两个、一列五个,那mod的基数和 Y 偏移都要改。别照抄,先拿一张空白标签试打,用尺子量实际位置再调参数。
校验位是另一个进阶点。Code39 的 Mod 43 校验、Code128 的 Mod 103 校验,有些控件默认不算,需要手动补。以 Code39 为例,校验字符的计算逻辑是:把每个字符在字符表中的索引值相加,对 43 取模,余数对应的字符就是校验位,附在数据末尾、终止符之前。如果控件不自动加,就得自己写个函数算出来拼到Data后面。扫描枪那边如果设了"校验位检查",不加校验位就会拒读;如果没设,加了也不影响。我一般建议加上,多一个字符换更高的首读率,值。
最后说个验证方法:生成条码后,别只用一把扫描枪试。找两把不同品牌、不同年代的枪,一把激光一把影像,各扫十次。都过,才算稳。从那以后我每次交付条码功能前,都强制走一遍"两枪各十次"的流程,再也没被现场退货过。希望帮到你。
本文还有配套的精品资源,点击获取