简介这份资源面向使用 Delphi 进行桌面应用开发、并需要在程序中集成标签打印功能的开发者核心解决如何通过 ActiveX 控件在 Delphi 环境下调用 Bartender 完成标签设计与打印的问题。资源包内共 1 个 doc 文档压缩包约 392KB内容围绕 Bartender 的 ActiveX 调用方式展开涵盖控件安装、项目创建、控件引入、API 调用及功能测试等关键环节并梳理了标签设计、打印输出、数据库管理等主要功能模块同时说明了该方案在提升工作效率、增强灵活性和控制成本方面的优势。目前已有 389 人学习浏览适合具备一定 Delphi 基础、希望快速掌握 Bartender 集成思路的开发者参考可帮助读者理解 ActiveX 调用流程与常见功能接口为实际项目中的标签打印需求提供可落地的实现方向。1. 从一张标签说起Delphi 调 Bartender 到底在调什么车间里那台老产线还在跑工控机上挂着一个 Delphi 写的进销存操作员点一下“打印标签”旁边那台条码机就吐出一张带批次号的标签。这套东西看着土但背后其实是一个很典型的组合Delphi 负责业务逻辑和界面Bartender 负责标签模板和打印驱动。两者之间靠的不是什么神秘协议就是 ActiveX 自动化。很多人第一次听到“Delphi 调用 Bartender”会以为要装什么 SDK、写什么驱动其实 Bartender 本身就暴露了一个 ActiveX 控件Delphi 作为老牌 COM 宿主直接导入类型库就能用。核心对象就一个BarTender.Application拿到它之后打开模板、改字段、发打印指令三步走完。适合谁适合手里有存量 Delphi 项目、又不想把标签打印重写成 .NET 或者 Python 的团队。下面把我自己拆这套东西的过程和踩过的坑摊开讲。2. 把 ActiveX 导进 Delphi类型库导入与对象模型2.1 为什么是 ActiveX 而不是命令行Bartender 其实提供了好几种被调用的方式常见的有命令行参数、ActiveX 自动化、还有 .NET 的 SDK。命令行最省事但只能传有限参数改字段要靠/F加文件路径动态性差.NET SDK 功能全但 Delphi 调 .NET 得走 COM 互操作或者托管封装多一层就多一层玄学。ActiveX 自动化是中间那条路Bartender 安装时注册了BarTender.Application这个 ProgIDDelphi 通过 COM 直接拿到接口既能读模板里的具名字段也能触发打印还能拿到打印状态。选它的理由很实在不用额外装运行时不用改项目架构Delphi 7 到 Delphi 11 都能用。代价是 Bartender 必须是完整版或者至少是支持自动化的版本某些精简版或者试用版在自动化接口上会有限制这个后面避坑章节会细说。2.2 在 Delphi IDE 里导入类型库操作路径不复杂但版本差异会让人翻车。以 Delphi 10.x 为例打开项目菜单Component→Import Component。选Import a Type Library在列表里找BarTender 20xx Type Library版本号取决于你装的 Bartender 年份。单位名默认是BarTender_TLB别改后面引用方便。勾选Generate Component Wrapper会多生成一个组件面板页不勾也行直接CreateUnit方式用接口。导入完成后Delphi 会在项目里生成一个.pas文件里面定义了IApplication、IFormat、IDocument这些接口。这里有个细节不同年份的 Bartender 类型库接口名可能微调比如早期版本IApplication的方法叫Open新版本可能多了重载。导入后先别急着写业务编译一下看有没有E2010 Incompatible types之类的报错有的话说明类型库和当前 Delphi 的 COM 声明有冲突常见做法是手动改一下生成的.pas里那几个IDispatch的声明。// 导入类型库后在 uses 里加上 BarTender_TLB uses ComObj, ActiveX, BarTender_TLB; var BtApp: IApplication; BtFormat: IFormat; begin // 创建 Bartender 应用实例ProgID 就是注册表里的名字 BtApp : CoApplication.Create; try // 打开一个已经设计好的标签模板路径写绝对路径 BtFormat : BtApp.Formats.Open(D:\Labels\product_label.btw, False, ); // 改一个具名字段字段名要和模板里定义的完全一致 BtFormat.SetNamedSubStringValue(BatchNo, 20240521-001); // 打印一份到默认打印机 BtFormat.PrintOut(False, False); finally // 退出 Bartender释放 COM 对象 BtApp.Quit(btDoNotSaveChanges); BtApp : nil; end; end;这段代码的逻辑很直白CoApplication.Create走的是 COM 的类工厂Formats.Open打开模板SetNamedSubStringValue改字段PrintOut触发打印。参数说明一下Open的第二个参数是ReadOnly传False表示可写第三个参数是Password模板没加密就传空串。PrintOut第一个参数ShowPrintDialog传False表示不弹窗直接打第二个参数WaitForCompletion传False表示异步打完就返回不等打印机响应。2.3 对象模型里真正要关心的三个接口导入之后接口一大堆但日常用的就三个接口作用关键方法IApplication应用级入口Formats.Open、QuitIFormat单个标签模板SetNamedSubStringValue、PrintOutIDocument打印任务Print、WaitForCompletionIApplication是根Formats是个集合Open返回IFormat。IFormat上除了改字段还能读数据库连接、改打印机名。IDocument一般不用手动创建PrintOut内部会生成。如果你要精确控制打印份数用IFormat.PrintOut的第三个参数Copies或者拿IDocument的Print方法。3. 动态改字段与打印从模板具名到数据库联动3.1 具名字段和数据库字段的区别Bartender 模板里改数据有两种路子一种是具名字段Named SubString一种是数据库字段绑定。具名字段是模板设计时手动在对象属性里起的名字比如BatchNo、ProductName调用方通过SetNamedSubStringValue直接塞值。数据库字段是模板里配了 ODBC 或者文本文件数据源打印时 Bartender 自己去查。选哪个如果数据来自 Delphi 的业务逻辑比如刚算出来的批次号用具名字段最直接不用配数据源。如果数据量大、字段多比如从 SQL Server 里拉一整批产品信息那在模板里配数据库连接更省事Delphi 只负责触发打印。我一般混合用关键动态字段用具名静态的或者批量数据用数据库绑定。// 假设模板里配了一个文本文件数据源路径是 D:\Data\products.txt // 这里演示用具名字段覆盖其中一列再触发打印 BtFormat.SetNamedSubStringValue(ProductName, 工业级条码打印机); BtFormat.SetNamedSubStringValue(Price, 1280.00); // 如果模板里绑了数据库可以强制刷新数据源 BtFormat.Database.Refresh; // 打印两份不弹窗异步 BtFormat.PrintOut(False, False, 2);Database.Refresh这个调用在数据库源是文件时特别有用因为 Bartender 默认会缓存文件内容不刷新的话打出来的还是上一次的数据。PrintOut第三个参数是份数传2就是打两张。注意异步模式下如果连续快速调用PrintOutBartender 内部会排队但打印机可能跟不上后面避坑章节会讲怎么加延迟。3.2 打印机选择和页面设置Bartender 模板里可以预设打印机但工控机上打印机名可能变比如从Zebra ZT230换成TSC TTP-244。这时候在 Delphi 里动态改打印机比重新设计模板快。// 获取当前模板的打印机设置 BtFormat.Printer TSC TTP-244 Pro; // 如果模板里页面大小和实际标签纸不匹配可以改页面设置 BtFormat.PageSetup.LabelWidth : 60; // 单位是毫米 BtFormat.PageSetup.LabelHeight : 40; // 打印前先预览一下确认布局没跑偏 BtFormat.PrintPreview;Printer属性直接赋打印机名字符串名字要和 Windows 里显示的完全一致包括空格和大小写。PageSetup里的宽高单位取决于 Bartender 的全局设置默认是毫米但有些模板用的是英寸改之前先在 Bartender 界面里看一眼。PrintPreview在调试阶段很有用能弹出一个预览窗口确认字段替换和布局都对了再正式打。3.3 异常处理和 COM 释放COM 对象不释放Bartender 进程会残留在任务管理器里打几次之后内存就上去了。Delphi 里IApplication是接口引用计数归零会自动释放但前提是你没在全局变量里一直持有它。procedure TForm1.PrintLabel(const BatchNo: string); var BtApp: IApplication; BtFormat: IFormat; begin BtApp : nil; BtFormat : nil; try BtApp : CoApplication.Create; BtFormat : BtApp.Formats.Open(D:\Labels\product_label.btw, False, ); BtFormat.SetNamedSubStringValue(BatchNo, BatchNo); BtFormat.PrintOut(False, False); except on E: Exception do begin // 记录日志别让异常吞掉 LogError(Bartender print failed: E.Message); end; end; // 显式置 nil触发引用计数释放 BtFormat : nil; if Assigned(BtApp) then begin BtApp.Quit(btDoNotSaveChanges); BtApp : nil; end; end;这段代码的关键在finally之前就把BtFormat置 nil然后Quit再置 nil。顺序反了的话Quit之后BtFormat还持有引用Bartender 进程可能不会立刻退出。btDoNotSaveChanges是枚举值表示退出时不保存模板改动避免误改模板文件。4. 避坑与排查那些让我加班到凌晨的报错4.1 “Class not registered” 或 “ActiveX component cant create object”现象CoApplication.Create直接抛异常提示类未注册。原因Bartender 安装时没有注册 ActiveX 组件或者装的是精简版/试用版自动化接口被阉割。还有一种可能是 32 位和 64 位不匹配——Delphi 编译的是 32 位程序但 Bartender 装的是 64 位版本注册表里 ProgID 在Wow6432Node下找不到。解决先确认 Bartender 版本完整版在安装时会有“注册 ActiveX 组件”的选项没勾的话重新运行安装程序修复。然后确认 Delphi 目标平台是 32 位还是 64 位和 Bartender 的位数对齐。如果实在对不齐常见做法是用一个 32 位的中间进程去调 BartenderDelphi 通过命名管道或者消息和中间进程通信但这属于绕路方案能不用就不用。4.2 打印出来字段没变还是上一次的数据现象代码里明明改了BatchNo打出来的标签还是旧值。原因Bartender 对模板里的数据库源有缓存尤其是文本文件或者 Excel 数据源SetNamedSubStringValue改的是具名字段但如果模板里那个对象绑的是数据库字段具名字段就被覆盖了。另一种情况是模板打开时用了ReadOnlyTrue改字段被静默忽略。解决先确认模板里那个对象到底是具名字段还是数据库字段在 Bartender 设计器里看对象属性。如果是数据库字段要么改成具名字段要么在改完之后调Database.Refresh。Open的第二个参数确保传False。还有个小概率情况模板文件被其他进程占用Open打开的是缓存副本改完没保存到实际打印的实例上这种就检查文件锁。4.3 连续打印时 Bartender 进程堆积现象批量打印 100 张标签任务管理器里出现几十个 Bartender 进程内存飙升。原因每次CoApplication.Create都新建一个 Bartender 实例如果没正确Quit进程就残留。异步PrintOut还没完成时Quit被调用Bartender 可能挂起不退出。解决批量打印时不要每次新建实例复用一个IApplication打完所有标签再Quit。如果必须每次新建在PrintOut之后加一个等待确认打印任务进入队列再退出。等待可以用IDocument.WaitForCompletion或者简单点用Sleep(500)但 Sleep 是下策能拿IDocument就拿。// 批量打印时复用实例的写法 BtApp : CoApplication.Create; try for i : 0 to BatchList.Count - 1 do begin BtFormat : BtApp.Formats.Open(D:\Labels\product_label.btw, False, ); BtFormat.SetNamedSubStringValue(BatchNo, BatchList[i]); BtFormat.PrintOut(False, False); BtFormat : nil; // 释放当前模板引用 end; finally BtApp.Quit(btDoNotSaveChanges); BtApp : nil; end;4.4 打印机名改了但打到了默认打印机现象代码里设了BtFormat.Printer : TSC TTP-244 Pro结果标签从办公室那台激光打印机出来了。原因Bartender 模板里可能勾了“使用默认打印机”这个选项优先级高于代码里的Printer赋值。或者打印机名拼写有细微差异比如多了个空格Bartender 找不到就回退到默认。解决在 Bartender 设计器里取消“使用默认打印机”的勾选改成指定打印机。代码里赋值后用BtFormat.Printer读回来确认一下读回来的名字和设进去的一致才说明生效了。如果打印机是网络打印机名字要用\\服务器名\打印机名的 UNC 格式。4.5 试用版打印随机改字符现象用试用版 Bartender 调试打出来的标签偶尔某个字符变成乱码或者问号。原因Bartender 试用版有个限制30 天到期后包含试用功能的标签格式在打印时会随机更改一个字符。这个不是代码问题是授权机制。解决换正式授权或者用 Bartender 的免费版功能有限但不会随机改字符。如果只是内部测试可以在虚拟机里定期重置试用期但这不是长久之计。生产环境必须用正式授权否则打出来的标签可能扫不出来这个坑我踩过一次一批货的条码全废了。5. 进阶用 Delphi 做一层打印队列封装5.1 为什么要封装直接在每个业务按钮里写CoApplication.Create和PrintOut短期能跑长期会乱。打印任务多了之后你需要控制并发、重试、日志、打印机状态回读。我一般会在 Delphi 里封一个TLabelPrinter类把 Bartender 的调用包起来业务层只调Print(BatchNo, ProductName)这样的方法。type TLabelPrinter class private FApp: IApplication; FTemplatePath: string; FPrinterName: string; function EnsureApp: IApplication; public constructor Create(const ATemplatePath, APrinterName: string); destructor Destroy; override; function Print(const Fields: TStrings; Copies: Integer 1): Boolean; end; function TLabelPrinter.EnsureApp: IApplication; begin if not Assigned(FApp) then FApp : CoApplication.Create; Result : FApp; end; function TLabelPrinter.Print(const Fields: TStrings; Copies: Integer): Boolean; var BtFormat: IFormat; i: Integer; begin Result : False; try BtFormat : EnsureApp.Formats.Open(FTemplatePath, False, ); BtFormat.Printer : FPrinterName; for i : 0 to Fields.Count - 1 do BtFormat.SetNamedSubStringValue(Fields.Names[i], Fields.ValueFromIndex[i]); BtFormat.PrintOut(False, False, Copies); BtFormat : nil; Result : True; except on E: Exception do LogError(Print failed: E.Message); end; end;这个封装的逻辑是EnsureApp懒加载 Bartender 实例避免每次新建Print接收一个TStrings里面是字段名值的键值对循环塞进模板异常统一记日志返回布尔值让业务层知道成功与否。Copies默认 1需要多份就传。5.2 打印状态回读和重试Bartender 的PrintOut异步模式下你只知道指令发出去了不知道打印机有没有真的打。要确认状态得拿IDocument接口。var Doc: IDocument; begin Doc : BtFormat.PrintOut(False, True); // 第二个参数 True 表示等待完成 if Doc.Status btStatusCompleted then LogInfo(Print completed) else LogWarn(Print status: IntToStr(Ord(Doc.Status))); end;PrintOut的第二个参数传True会阻塞直到打印完成返回IDocument。Doc.Status是个枚举btStatusCompleted表示成功其他值对应错误、取消、缺纸等。重试逻辑就围绕这个状态写如果状态不是完成等两秒再试一次最多三次。注意阻塞模式下别在主线程里调会卡界面放到线程里或者用TThread.Queue异步处理。5.3 一个我常用的调试习惯每次改完模板或者打印逻辑我不会直接上产线而是先在 Delphi 里调PrintPreview把预览窗口弹出来看一眼。确认字段替换、打印机、份数都对了再改成PrintOut正式打。这个习惯帮我省了很多废标签纸。另外模板文件我会用 Git 管起来每次改动都有记录出问题能回滚。Bartender 的.btw文件是二进制Git 不能 diff但至少能看出版本变化。从那以后我每次接新的 Bartender 调用需求都强制走一遍先导入类型库编译通过再写最小打印 Demo确认字段和打印机都对最后才往业务代码里集成。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?