
简介这份资源面向使用 Delphi 进行桌面应用开发、并需要在程序中集成标签打印功能的开发者核心解决如何通过 ActiveX 控件在 Delphi 环境下调用 Bartender 实现标签设计与打印的问题。资源包内共 1 个 doc 文档压缩包约 392KB内容围绕 Bartender 的 ActiveX 调用方式展开涵盖控件安装、项目创建、控件添加、API 调用及功能测试等关键环节并介绍了 Bartender 在标签设计、打印输出和数据库管理方面的主要能力。目前已有 389 人学习浏览适合具备一定 Delphi 基础、希望将标签打印能力嵌入业务系统的技术人员参考。读者可从中了解 ActiveX 集成的基本流程与调用思路为条码标签、票据打印等实际场景提供可落地的实现参考。1. 车间那台老打印机为什么还在用 Delphi 调 Bartender如果你在制造业、仓储或零售的 IT 部门待过大概率见过这样的场景一台工控机上跑着十几年前用 Delphi 写的 MES 或 WMS 客户端操作员点一下“打印标签”旁边那台条码打印机就吐出一张带序列号、批次号和二维码的标签。这套流程背后十有八九是 Delphi 通过 ActiveX 调用了 Bartender 的打印引擎。听起来像是上个时代的方案但它至今仍在大量产线上稳定运行原因很简单——Bartender 的标签模板设计能力足够强而 Delphi 的原生 Windows 集成能力让调用变得极其直接。这份资源就是围绕“Delphi 环境下调用 Bartender”这个具体场景展开的适合正在维护老系统、需要把标签打印模块从 Bartender 手工操作改成程序自动触发的开发者。它不讲 Bartender 怎么拖控件画标签而是讲怎么让 Delphi 代码把数据塞进模板、触发打印、拿到结果。2. 先搞懂 Bartender 的 ActiveX 接口它到底暴露了什么2.1 Bartender 的自动化对象模型Bartender 对外提供的是一套 COM 自动化接口核心对象是BarTender.Application。这个对象在注册表里注册为BarTender.ApplicationDelphi 通过CreateOleObject或者导入类型库的方式拿到它。拿到之后整个调用链路是这样的Application 对象下挂Formats集合每个 Format 对应一个.btw模板文件Format 下挂Databases集合用来操作模板里绑定的数据库字段打印动作通过 Format 的PrintOut方法触发或者通过 Application 的Print方法批量触发。这套模型的设计逻辑是“模板即数据容器”你在 Bartender 设计器里把标签画好、把变量字段命名好Delphi 端只负责往这些命名变量里填值然后调打印。常见做法是模板里用“命名数据源”定义字段比如SerialNo、BatchNo、QRCodeDelphi 端通过Format.Databases或者Format.NamedSubStrings来赋值。2.2 在 Delphi 里引入类型库Delphi 调用 COM 有两种方式晚绑定CreateOleObject和早绑定导入类型库生成_TLB.pas。晚绑定写起来快但参数类型全靠 Variant调试时容易翻车早绑定有代码提示参数类型明确适合长期维护的项目。我一般会优先用早绑定。操作路径是Delphi IDE 菜单Component→Import Component→Import a Type Library→ 找到BarTender 20xx或类似名称的库 → 生成BarTender_TLB.pas。生成之后单元里会多出BarTender_TLB的引用代码里可以直接声明CoBarTenderApplication和BarTenderApplication接口类型。注意不同版本的 Bartender 类型库名称可能略有差异但核心接口名基本稳定。// 早绑定方式使用导入的类型库 uses ComObj, ActiveX, BarTender_TLB; procedure TForm1.PrintLabel; var BtApp: BarTenderApplication; BtFormat: BarTenderFormat; begin // 创建 Bartender 应用实例 BtApp : CoBarTenderApplication.Create; try // 打开已经设计好的标签模板 BtFormat : BtApp.Formats.Open(D:\Labels\ProductLabel.btw, False, ); try // 给模板里的命名数据源赋值 BtFormat.SetNamedSubStringValue(SerialNo, SN20250101-001); BtFormat.SetNamedSubStringValue(BatchNo, B20250101); BtFormat.SetNamedSubStringValue(QRCode, https://example.com/trace/SN20250101-001); // 触发打印参数依次为打印机名、份数、是否弹出打印对话框 BtFormat.PrintOut(Zebra ZT410, 1, False); finally BtFormat.Close(btDoNotSaveChanges); end; finally BtApp.Quit(btDoNotSaveChanges); end; end;这段代码的逻辑很直白创建应用实例、打开模板、填值、打印、关闭。参数说明上Formats.Open的第二个参数是“是否以只读方式打开”第三个参数是“密码”没有密码就传空字符串。PrintOut的第一个参数是打印机名称必须和 Windows 里安装的打印机名称完全一致大小写敏感第二个参数是打印份数第三个参数如果传True会弹出 Bartender 的打印对话框让操作员确认自动化场景一般传False。Close和Quit的参数用来控制是否保存修改自动化打印不需要保存模板改动所以传btDoNotSaveChanges。2.3 晚绑定方式的适用场景有些老项目用的是 Delphi 5 或 Delphi 7导入类型库可能遇到兼容性问题这时候晚绑定反而更省事。晚绑定的代码长这样// 晚绑定方式不依赖类型库全部用 Variant procedure TForm1.PrintLabelLateBinding; var BtApp, BtFormat: Variant; begin BtApp : CreateOleObject(BarTender.Application); try BtFormat : BtApp.Formats.Open(D:\Labels\ProductLabel.btw, False, ); BtFormat.SetNamedSubStringValue(SerialNo, SN20250101-002); BtFormat.SetNamedSubStringValue(BatchNo, B20250101); BtFormat.PrintOut(Zebra ZT410, 1, False); BtFormat.Close(1); // 1 对应 btDoNotSaveChanges finally BtApp.Quit(1); end; end;晚绑定的坑在于所有方法名和参数都是运行时解析写错了编译期不报错运行时才抛异常。而且Close和Quit的参数需要用数字常量不能直接用枚举名。所以如果项目允许还是建议早绑定。3. 从零跑通一个打印 Demo项目结构、依赖和完整流程3.1 项目目录怎么摆一个最小可运行的 Delphi 调用 Bartender 的 Demo目录结构不需要太复杂但要把模板文件、输出日志和源码分开。我一般这样组织LabelPrintDemo/ ├── src/ │ ├── MainForm.pas // 主窗体放打印按钮和日志框 │ ├── MainForm.dfm │ └── LabelPrintDemo.dpr // 项目文件 ├── labels/ │ └── ProductLabel.btw // Bartender 模板文件 ├── lib/ │ └── BarTender_TLB.pas // 导入的类型库单元 └── logs/ └── print.log // 打印日志排查问题时用模板文件放在labels目录下代码里用相对路径或配置项读取不要硬编码绝对路径。类型库单元放在lib下项目搜索路径里加上lib。日志目录用来记录每次打印的模板名、打印机名、关键字段值和返回结果出问题时这是唯一的黑匣子。3.2 窗体上的关键控件和事件主窗体上放一个TButton叫btnPrint一个TMemo叫mmoLog再加一个TEdit叫edtSerialNo用来手动输入序列号。按钮的OnClick事件里调用打印过程打印前后往mmoLog里写日志。关键代码如下procedure TForm1.btnPrintClick(Sender: TObject); var BtApp: BarTenderApplication; BtFormat: BarTenderFormat; SerialNo: string; begin SerialNo : Trim(edtSerialNo.Text); if SerialNo then begin mmoLog.Lines.Add(Format([%s] 序列号为空取消打印, [DateTimeToStr(Now)])); Exit; end; try BtApp : CoBarTenderApplication.Create; try BtFormat : BtApp.Formats.Open(ExtractFilePath(ParamStr(0)) labels\ProductLabel.btw, False, ); try BtFormat.SetNamedSubStringValue(SerialNo, SerialNo); BtFormat.SetNamedSubStringValue(BatchNo, FormatDateTime(yyyymmdd, Now)); BtFormat.SetNamedSubStringValue(QRCode, TRACE: SerialNo); BtFormat.PrintOut(Zebra ZT410, 1, False); mmoLog.Lines.Add(Format([%s] 打印成功%s, [DateTimeToStr(Now), SerialNo])); finally BtFormat.Close(btDoNotSaveChanges); end; finally BtApp.Quit(btDoNotSaveChanges); end; except on E: Exception do begin mmoLog.Lines.Add(Format([%s] 打印异常%s, [DateTimeToStr(Now), E.Message])); end; end; end;这段代码比第 2 章的示例多了几个工程化细节序列号从界面控件读取并做空值校验模板路径用ExtractFilePath(ParamStr(0))拼相对路径避免硬编码异常被捕获后写入日志而不是直接弹框产线环境里弹框会阻塞操作员。BatchNo用当前日期生成QRCode拼了一个前缀这些字段名必须和 Bartender 模板里的命名数据源完全一致否则赋值会静默失败——这是最常见的翻车点之一。3.3 模板端要配合做什么Delphi 端代码写对了模板端没配好照样打不出来。在 Bartender 设计器里你需要做三件事第一在标签上放置文本或条码对象双击对象在“数据源”里选择“命名数据源”输入字段名比如SerialNo第二确保模板里没有残留的“数据库连接”指向不存在的文件否则打开模板时会报错第三在“打印”设置里把打印机选好或者留空让 Delphi 端通过PrintOut参数指定。模板保存为.btw格式不要保存为.btw的旧版本兼容格式除非你的 Bartender 版本确实很老。提示模板里的命名数据源区分大小写Delphi 端传的字段名必须和模板里定义的完全一致。建议在模板里用统一的命名规范比如全部小写加下划线避免混淆。4. 避坑与排查那些年我们踩过的打印异常4.1 报“未找到打印机”但打印机明明在现象是 Delphi 端调用PrintOut时抛出异常提示打印机不存在或不可用。原因通常是打印机名称不匹配——Windows 里显示的打印机名称可能带空格或后缀比如Zebra ZT410 (副本 1)而代码里只写了Zebra ZT410。解决方法是打开“设备和打印机”右键打印机 → 属性看“常规”选项卡里的完整名称一字不差地复制到代码里。如果打印机是通过网络共享的还要确保 Delphi 进程有权限访问网络打印机。4.2 模板打开成功但打印出来是空白现象是PrintOut没报错但打印机吐白纸。原因多半是模板里的命名数据源没有正确绑定到对象上或者 Delphi 端赋值的字段名和模板里定义的不一致。排查方法是在 Bartender 设计器里手动打印一次看是否能打出内容如果能说明模板本身没问题问题在 Delphi 端的字段名或赋值时机。另一个可能原因是模板里用了“数据库字段”而不是“命名数据源”这两种数据源在 API 里的赋值方式不同命名数据源用SetNamedSubStringValue数据库字段需要操作Databases集合。4.3 多次打印后 Bartender 进程残留现象是每次打印都创建一个新的 Bartender 进程任务管理器里一堆BarTender.exe。原因是Quit没有正确调用或者Close时抛了异常导致Quit被跳过。解决方法是用try...finally确保Quit一定执行并且在Quit之前先Close所有打开的 Format。如果还是残留可以在Quit之后加一句Sleep(200)给 COM 释放留点时间。更彻底的做法是复用同一个 Application 实例不要每次打印都Create一个新实例。4.4 打印速度慢一张标签要等好几秒现象是每次打印都感觉 Bartender 在“启动”耗时明显。原因是每次调用都创建了新的 Application 实例Bartender 的初始化开销不小。优化方法是在程序启动时创建一个全局的BarTenderApplication实例打印时只打开模板、赋值、打印、关闭模板不退出应用程序退出时再统一Quit。这样能把单张打印耗时从几秒降到几百毫秒。注意全局实例在多线程环境下需要加锁产线客户端一般是单线程操作问题不大。4.5 权限问题导致 COM 创建失败现象是CoBarTenderApplication.Create抛出“拒绝访问”或“类未注册”。原因可能是 Bartender 安装时没有注册 COM 组件或者当前 Windows 用户没有权限。解决方法是以管理员身份运行一次 Bartender让它完成 COM 注册如果还不行用regsvr32手动注册 Bartender 安装目录下的BarTend.exe或相关 DLL。另外如果 Delphi 程序是以服务方式运行的需要把服务登录账户改成有桌面交互权限的账户因为 Bartender 的 COM 组件需要访问桌面。5. 二次开发怎么改从单张打印到批量任务和模板热切换5.1 把打印逻辑抽成独立单元Demo 里把打印代码写在窗体事件里实际项目里应该抽成一个独立的单元比如LabelPrinter.pas对外暴露一个PrintLabel(const APrinterName, ATemplatePath: string; AFields: TStrings): Boolean函数。这样窗体只负责收集数据打印逻辑可以复用、可以单元测试、可以在服务端调用。抽出来之后换打印机、换模板、加字段都只改一个地方。5.2 支持批量打印和队列产线上经常需要一次打一批标签比如一个工单 50 个序列号。批量打印的关键是复用 Application 实例和 Format 实例打开一次模板循环赋值、循环PrintOut最后统一关闭。不要每张标签都重新打开模板那样速度会慢十倍。如果打印量很大可以加一个简单的队列把打印任务丢进TThreadedQueue后台线程慢慢打界面不卡。// 批量打印示例复用 Format 实例 procedure TLabelPrinter.PrintBatch(const APrinterName, ATemplatePath: string; ASerialNos: TStrings); var BtApp: BarTenderApplication; BtFormat: BarTenderFormat; I: Integer; begin BtApp : CoBarTenderApplication.Create; try BtFormat : BtApp.Formats.Open(ATemplatePath, False, ); try for I : 0 to ASerialNos.Count - 1 do begin BtFormat.SetNamedSubStringValue(SerialNo, ASerialNos[I]); BtFormat.SetNamedSubStringValue(BatchNo, FormatDateTime(yyyymmdd, Now)); BtFormat.PrintOut(APrinterName, 1, False); end; finally BtFormat.Close(btDoNotSaveChanges); end; finally BtApp.Quit(btDoNotSaveChanges); end; end;这段代码的核心优化点是Formats.Open只调一次循环里只做赋值和打印。参数ASerialNos是一个字符串列表调用方把要打印的序列号都塞进去。实际项目中还可以加打印计数、失败重试、日志记录。5.3 模板热切换和字段映射配置化不同产品用不同模板硬编码模板路径迟早要改代码。我一般会把模板路径和字段映射写进一个 INI 或 JSON 配置文件程序启动时加载。配置结构大概是产品编码 → 模板文件路径 → 字段映射表Delphi 字段名 → Bartender 命名数据源名。这样新增产品只需要加配置和模板文件不用重新编译程序。字段映射表还能解决 Delphi 端字段名和模板字段名不一致的问题中间加一层转换。5.4 验证改造是否成功的三个检查点改完之后怎么确认没改坏第一用同一批序列号连续打印 100 张看是否有漏打、重打、错打第二手动在 Bartender 里打开模板确认命名数据源没有被意外修改第三观察任务管理器确认打印结束后没有残留的BarTender.exe进程。这三个检查点走一遍基本能覆盖大部分回归问题。从那以后我每次改打印模块都会先跑一遍批量打印测试再检查进程残留最后才交给产线试用。希望帮到你。本文还有配套的精品资源点击获取