Delphi通过ActiveX调用Bartender实现标签打印:从集成到避坑
简介:这份资源面向使用 Delphi 进行桌面应用开发、并需要集成标签打印功能的开发者,核心解决在 Delphi 环境下调用 Bartender 实现标签设计与打印的问题。资源包内共 1 个 doc 文档,压缩包约 392KB,文档围绕 Bartender 的 ActiveX 控件调用方式展开,涵盖控件安装、项目创建、控件添加、API 调用及功能测试等关键环节,同时介绍了 Bartender 在标签设计、打印输出和数据库管理方面的主要能力,并分析了该方案在提升工作效率、增强灵活性和控制成本上的优势。目前已有 389 人学习,适合希望快速掌握 Delphi 与 Bartender 集成思路、减少自行摸索时间的开发者参考,可作为实际项目落地时的技术选型与实现依据。
1. 从一张产线标签说起:Delphi 调 Bartender 到底在调什么
车间里经常有这种场景:MES 或 ERP 里点一下“打印标签”,操作员面前的条码打印机就吐出一张带批次号、序列号、二维码的标签。界面是 Delphi 写的,标签模板却是用 Bartender 设计的。很多人第一次接这个需求时,以为要在 Delphi 里画标签、拼 ZPL 指令,结果绕了一大圈才发现——真正省事的做法是让 Delphi 通过 ActiveX 去驱动 Bartender,模板归模板,数据归数据。
这份资源讲的就是这条链路:Delphi 环境下调用 Bartender,把数据库里的字段塞进已经设计好的 .btw 模板,再触发打印。它解决的不是“怎么设计标签”,而是“怎么让业务系统自动出标签”。适合两类人:一类是维护老 Delphi 产线系统的工程师,另一类是刚接手条码打印模块、被 ActiveX 和 COM 接口绕晕的新手。下面按“能跑起来”的标准,把控件引入、API 调用、数据库联动和踩坑点拆开讲。
2. 把 Bartender 装进 Delphi:ActiveX 引入与工程配置
2.1 先确认 Bartender 版本与 ActiveX 可用性
Bartender 的自动化能力来自它注册的 ActiveX 控件,常见做法是安装完整版后,在系统里注册BarTender.Application这个 COM 对象。这里有个前提:安装时不能只勾“打印客户端”,要确保自动化组件被注册。判断方法很简单,在 Delphi 里走一遍Component → Import Component → Import ActiveX Control,如果列表里能看到 BarTender 相关的类型库,说明注册成功;看不到,多半是安装选项或权限问题。
需要留意的是,不同大版本的 Bartender 在自动化接口上会有差异,但核心对象模型基本稳定:Application是入口,Format代表一个 .btw 模板,SubStrings用来改模板里的命名数据源。我一般会先在 Bartender 里把模板调通,确认手动打印没问题,再进 Delphi 写代码,这样能把“模板问题”和“代码问题”分开排查。
2.2 在 Delphi 中导入类型库并生成包装单元
导入之后,Delphi 会生成一个类似BarTender_TLB.pas的单元,里面封装了接口和 CoClass。工程里引用它,就可以直接声明BarTender.Application类型的变量。下面是一段最小可运行的调用骨架,重点看对象创建和释放的顺序:
uses ComObj, BarTender_TLB; // 导入生成的类型库单元 procedure TForm1.PrintLabel; var btApp: BarTender.Application; btFormat: BarTender.Format; begin // 创建 Bartender 应用实例,True 表示复用已运行的实例 btApp := CoApplication.Create; try // 打开已经设计好的模板文件,路径用绝对路径最稳 btFormat := btApp.Formats.Open('D:\Labels\product.btw', False, True); try // 修改模板里的命名数据源,名字要和 Bartender 里一致 btFormat.SetNamedSubStringValue('BatchNo', '20240520-A'); btFormat.SetNamedSubStringValue('SN', 'SN000123'); // 指定打印机并打印一份 btFormat.PrintOut(False, False); finally btFormat.Close(btSaveChanges); end; finally btApp.Quit(btDoNotSaveChanges); end; end;逻辑上分四步:建应用、开模板、改数据、打印并释放。参数上,Formats.Open的第二个参数控制是否以只读方式打开,第三个参数控制是否显示 Bartender 界面,产线环境一般设成不显示,避免弹窗卡住流程。PrintOut的两个布尔值分别对应“是否弹出打印对话框”和“是否等待打印完成”,自动化场景通常都传 False。Quit里传btDoNotSaveChanges,防止误改模板被保存。
2.3 工程编译与运行环境检查
代码能编译不代表能跑。Delphi 生成的程序在目标机器上运行时,目标机同样要装 Bartender 并注册 ActiveX,否则CoApplication.Create会直接抛异常。常见做法是在安装包里带上 Bartender 运行环境,或者至少在部署文档里写清楚依赖。另外,32 位和 64 位要对齐:如果 Delphi 编译的是 32 位程序,就要确认注册的是 32 位 ActiveX,反之亦然,这一点在 64 位系统上特别容易翻车。
3. 用 API 驱动模板:数据源、打印控制与数据库联动
3.1 命名数据源与 SubString 的对应关系
Bartender 模板里改数据,靠的是“命名数据源”。在 Bartender 设计界面里,把某个文本对象的数据源设成“命名数据源”,起个名字比如BatchNo,Delphi 里就用SetNamedSubStringValue('BatchNo', 值)去填。名字必须完全一致,大小写敏感,拼错不会报错,只会打印出空值——这是最常见的“玄学”问题之一。
如果模板里用的是数据库字段,也可以走SetNamedSubStringValue覆盖,但更规范的做法是让模板连数据库、Delphi 只负责触发和传参。两种方式各有适用场景:字段少、变化快,用命名数据源直接塞;字段多、结构固定,让模板查库更省事。
3.2 从 Delphi 数据集批量打印
产线经常要一次打一批,比如一张订单几十个序列号。思路是把 Delphi 数据集里的每一行映射成一次模板数据替换,然后连续打印。下面这段演示从ADOQuery遍历并逐条打印:
procedure TForm1.PrintBatch; var btApp: BarTender.Application; btFormat: BarTender.Format; begin btApp := CoApplication.Create; try btFormat := btApp.Formats.Open('D:\Labels\product.btw', False, True); try ADOQuery1.First; while not ADOQuery1.Eof do begin // 每条记录替换一次数据源 btFormat.SetNamedSubStringValue('SN', ADOQuery1.FieldByName('SN').AsString); btFormat.SetNamedSubStringValue('BatchNo', ADOQuery1.FieldByName('Batch').AsString); // 打印一份,不弹窗、不等待 btFormat.PrintOut(False, False); ADOQuery1.Next; end; finally btFormat.Close(btSaveChanges); end; finally btApp.Quit(btDoNotSaveChanges); end; end;这里的关键是PrintOut放在循环里,每次替换数据后立即打印。参数含义和单张打印一致。需要提醒的是,如果打印机是共享队列,连续快速提交可能造成队列堆积,实际项目里我一般会在每次打印后加一个短延时,或者改用 Bartender 的批量打印接口,减少 COM 调用次数。
3.3 打印参数与打印机选择
PrintOut之外,Bartender 还提供了设置打印机、份数、序列号递增等能力。常见做法是通过btFormat.Printer指定目标打印机名称,名称要和系统里显示的完全一致。份数可以用IdenticalCopiesOfLabel属性设置。如果模板里本身带了序列化设置,Delphi 端就不要重复递增,否则会出现跳号。下面这张表整理了常用属性和含义,方便对照:
| 属性/方法 | 作用 | 常用取值 |
|---|---|---|
Formats.Open | 打开模板 | 路径、只读标志、是否显示界面 |
SetNamedSubStringValue | 替换命名数据源 | 名称、字符串值 |
PrintOut | 触发打印 | 是否弹窗、是否等待 |
Printer | 指定打印机 | 系统打印机名称 |
IdenticalCopiesOfLabel | 设置份数 | 整数 |
Quit | 退出应用 | 是否保存更改 |
把这些参数摸清,基本就能覆盖大多数产线打印需求。剩下的就是稳定性和异常处理。
4. 避坑与排查:ActiveX 调用里最容易翻车的几件事
4.1 报“类未注册”或 CoApplication 创建失败
现象是运行到CoApplication.Create直接抛异常,提示类未注册。原因通常是目标机没装 Bartender,或者安装的是不注册 ActiveX 的版本,也可能是 32/64 位不匹配。解决办法:先在目标机用Component → Import Component验证类型库是否存在;确认存在后检查 Delphi 编译目标位数和 ActiveX 注册位数是否一致;最后确认运行账户有权限访问 COM 组件。
4.2 模板打开成功但打印出来是空值
现象是标签能打,但该填数据的地方全是空白。原因基本是命名数据源名字对不上,或者模板里根本没设成命名数据源。解决办法:回到 Bartender 设计界面,逐个检查数据源名称,确保和代码里SetNamedSubStringValue的第一个参数完全一致;如果模板用的是数据库字段,确认数据库连接在目标机上也能通。
4.3 打印速度慢或程序卡死
现象是点一次打印要等好几秒,或者界面直接无响应。原因通常是PrintOut的等待参数设成了 True,或者 Bartender 界面被显示出来。解决办法:把PrintOut第二个参数设为 False,Formats.Open的显示参数也设为 False;如果还是慢,检查打印机驱动和网络队列,必要时改用后台打印服务。
4.4 连续打印出现跳号或重复
现象是批量打印时序列号不连续,或者同一张打了两遍。原因可能是模板里开了序列化,Delphi 端又在递增,两边叠加;也可能是循环里PrintOut和Next的顺序写错。解决办法:序列化只在一端做,要么模板管,要么代码管;循环里严格按“替换数据 → 打印 → 下一条”的顺序走,打印失败时做好日志,方便回溯。
4.5 程序退出后 Bartender 进程残留
现象是 Delphi 程序关了,任务管理器里还有 Bartender 进程。原因是Quit没调用,或者异常路径下跳过了释放。解决办法:把btApp.Quit放在finally里,确保无论是否异常都执行;如果复用了已运行的实例,退出前确认是否真的需要关闭,避免影响其他调用方。
5. 进阶技巧:把打印封装成可复用单元与验证方法
走到这里,基本功能已经能跑通。但产线项目里,我习惯把 Bartender 调用封装成一个独立的打印服务单元,而不是散落在各个按钮事件里。这样做的好处是:模板路径、打印机名称、异常处理都集中管理,换模板或换打印机时只改一处。下面是一个简化版的封装思路,用类把常用操作包起来:
type TLabelPrinter = class private FApp: BarTender.Application; FFormat: BarTender.Format; public constructor Create(const TemplatePath: string); procedure SetField(const Name, Value: string); procedure Print(const PrinterName: string; Copies: Integer); destructor Destroy; override; end; constructor TLabelPrinter.Create(const TemplatePath: string); begin inherited Create; FApp := CoApplication.Create; FFormat := FApp.Formats.Open(TemplatePath, False, True); end; procedure TLabelPrinter.SetField(const Name, Value: string); begin FFormat.SetNamedSubStringValue(Name, Value); end; procedure TLabelPrinter.Print(const PrinterName: string; Copies: Integer); begin FFormat.Printer := PrinterName; FFormat.IdenticalCopiesOfLabel := Copies; FFormat.PrintOut(False, False); end; destructor TLabelPrinter.Destroy; begin if FFormat <> nil then FFormat.Close(btSaveChanges); if FApp <> nil then FApp.Quit(btDoNotSaveChanges); inherited; end;封装之后,业务代码只需要创建对象、设字段、调打印,释放交给析构函数。参数上,Printer和IdenticalCopiesOfLabel在每次打印前设置,避免模板里的默认值干扰。验证方法也很直接:先用一张测试模板,手动改几个字段打印,确认无误后再接入真实数据;批量打印前先用小批量跑一遍,检查序列号和份数是否符合预期。
从那以后我每次接打印模块,都强制先做三件事:确认目标机 ActiveX 注册、用最小模板跑通单张、再上批量。这套习惯帮我省掉了大量现场排查时间。希望帮到你。
本文还有配套的精品资源,点击获取