
簡介這份資源面向使用 Delphi 進行桌面應用開發(fā)、并需要在程序中集成標簽打印功能的開發(fā)者核心解決如何通過 ActiveX 控件在 Delphi 環(huán)境下調用 Bartender 完成標簽設計與打印的問題。資源包內共 1 個 doc 文檔壓縮包約 392KB內容圍繞 Bartender 的 ActiveX 調用方式展開涵蓋控件安裝、項目創(chuàng)建、控件引入、API 調用及功能測試等關鍵環(huán)節(jié)并梳理了標簽設計、打印輸出、數據庫管理等主要功能模塊同時說明了該方案在提升工作效率、增強靈活性和控制成本方面的優(yōu)勢。目前已有 389 人學習瀏覽適合具備一定 Delphi 基礎、希望快速掌握 Bartender 集成思路的開發(fā)者參考可幫助讀者理解 ActiveX 調用流程與常見功能接口為實際項目中的標簽打印需求提供可落地的實現方向。1. 從一張標簽說起Delphi 調 Bartender 到底在調什么車間里那臺老產線還在跑工控機上掛著一個 Delphi 寫的進銷存操作員點一下“打印標簽”旁邊那臺條碼機就吐出一張帶批次號的標簽。這套東西看著土但背后其實是一個很典型的組合Delphi 負責業(yè)務邏輯和界面Bartender 負責標簽模板和打印驅動。兩者之間靠的不是什么神秘協議就是 ActiveX 自動化。很多人第一次聽到“Delphi 調用 Bartender”會以為要裝什么 SDK、寫什么驅動其實 Bartender 本身就暴露了一個 ActiveX 控件Delphi 作為老牌 COM 宿主直接導入類型庫就能用。核心對象就一個BarTender.Application拿到它之后打開模板、改字段、發(fā)打印指令三步走完。適合誰適合手里有存量 Delphi 項目、又不想把標簽打印重寫成 .NET 或者 Python 的團隊。下面把我自己拆這套東西的過程和踩過的坑攤開講。2. 把 ActiveX 導進 Delphi類型庫導入與對象模型2.1 為什么是 ActiveX 而不是命令行Bartender 其實提供了好幾種被調用的方式常見的有命令行參數、ActiveX 自動化、還有 .NET 的 SDK。命令行最省事但只能傳有限參數改字段要靠/F加文件路徑動態(tài)性差.NET SDK 功能全但 Delphi 調 .NET 得走 COM 互操作或者托管封裝多一層就多一層玄學。ActiveX 自動化是中間那條路Bartender 安裝時注冊了BarTender.Application這個 ProgIDDelphi 通過 COM 直接拿到接口既能讀模板里的具名字段也能觸發(fā)打印還能拿到打印狀態(tài)。選它的理由很實在不用額外裝運行時不用改項目架構Delphi 7 到 Delphi 11 都能用。代價是 Bartender 必須是完整版或者至少是支持自動化的版本某些精簡版或者試用版在自動化接口上會有限制這個后面避坑章節(jié)會細說。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這些接口。這里有個細節(jié)不同年份的 Bartender 類型庫接口名可能微調比如早期版本IApplication的方法叫Open新版本可能多了重載。導入后先別急著寫業(yè)務編譯一下看有沒有E2010 Incompatible types之類的報錯有的話說明類型庫和當前 Delphi 的 COM 聲明有沖突常見做法是手動改一下生成的.pas里那幾個IDispatch的聲明。// 導入類型庫后在 uses 里加上 BarTender_TLB uses ComObj, ActiveX, BarTender_TLB; var BtApp: IApplication; BtFormat: IFormat; begin // 創(chuàng)建 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觸發(fā)打印。參數說明一下Open的第二個參數是ReadOnly傳False表示可寫第三個參數是Password模板沒加密就傳空串。PrintOut第一個參數ShowPrintDialog傳False表示不彈窗直接打第二個參數WaitForCompletion傳False表示異步打完就返回不等打印機響應。2.3 對象模型里真正要關心的三個接口導入之后接口一大堆但日常用的就三個接口作用關鍵方法IApplication應用級入口Formats.Open、QuitIFormat單個標簽模板SetNamedSubStringValue、PrintOutIDocument打印任務Print、WaitForCompletionIApplication是根Formats是個集合Open返回IFormat。IFormat上除了改字段還能讀數據庫連接、改打印機名。IDocument一般不用手動創(chuàng)建PrintOut內部會生成。如果你要精確控制打印份數用IFormat.PrintOut的第三個參數Copies或者拿IDocument的Print方法。3. 動態(tài)改字段與打印從模板具名到數據庫聯動3.1 具名字段和數據庫字段的區(qū)別Bartender 模板里改數據有兩種路子一種是具名字段Named SubString一種是數據庫字段綁定。具名字段是模板設計時手動在對象屬性里起的名字比如BatchNo、ProductName調用方通過SetNamedSubStringValue直接塞值。數據庫字段是模板里配了 ODBC 或者文本文件數據源打印時 Bartender 自己去查。選哪個如果數據來自 Delphi 的業(yè)務邏輯比如剛算出來的批次號用具名字段最直接不用配數據源。如果數據量大、字段多比如從 SQL Server 里拉一整批產品信息那在模板里配數據庫連接更省事Delphi 只負責觸發(fā)打印。我一般混合用關鍵動態(tài)字段用具名靜態(tài)的或者批量數據用數據庫綁定。// 假設模板里配了一個文本文件數據源路徑是 D:\Data\products.txt // 這里演示用具名字段覆蓋其中一列再觸發(fā)打印 BtFormat.SetNamedSubStringValue(ProductName, 工業(yè)級條碼打印機); BtFormat.SetNamedSubStringValue(Price, 1280.00); // 如果模板里綁了數據庫可以強制刷新數據源 BtFormat.Database.Refresh; // 打印兩份不彈窗異步 BtFormat.PrintOut(False, False, 2);Database.Refresh這個調用在數據庫源是文件時特別有用因為 Bartender 默認會緩存文件內容不刷新的話打出來的還是上一次的數據。PrintOut第三個參數是份數傳2就是打兩張。注意異步模式下如果連續(xù)快速調用PrintOutBartender 內部會排隊但打印機可能跟不上后面避坑章節(jié)會講怎么加延遲。3.2 打印機選擇和頁面設置Bartender 模板里可以預設打印機但工控機上打印機名可能變比如從Zebra ZT230換成TSC TTP-244。這時候在 Delphi 里動態(tài)改打印機比重新設計模板快。// 獲取當前模板的打印機設置 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觸發(fā)引用計數釋放 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 連續(xù)打印時 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 模板里可能勾了“使用默認打印機”這個選項優(yōu)先級高于代碼里的Printer賦值?;蛘叽蛴C名拼寫有細微差異比如多了個空格Bartender 找不到就回退到默認。解決在 Bartender 設計器里取消“使用默認打印機”的勾選改成指定打印機。代碼里賦值后用BtFormat.Printer讀回來確認一下讀回來的名字和設進去的一致才說明生效了。如果打印機是網絡打印機名字要用\\服務器名\打印機名的 UNC 格式。4.5 試用版打印隨機改字符現象用試用版 Bartender 調試打出來的標簽偶爾某個字符變成亂碼或者問號。原因Bartender 試用版有個限制30 天到期后包含試用功能的標簽格式在打印時會隨機更改一個字符。這個不是代碼問題是授權機制。解決換正式授權或者用 Bartender 的免費版功能有限但不會隨機改字符。如果只是內部測試可以在虛擬機里定期重置試用期但這不是長久之計。生產環(huán)境必須用正式授權否則打出來的標簽可能掃不出來這個坑我踩過一次一批貨的條碼全廢了。5. 進階用 Delphi 做一層打印隊列封裝5.1 為什么要封裝直接在每個業(yè)務按鈕里寫CoApplication.Create和PrintOut短期能跑長期會亂。打印任務多了之后你需要控制并發(fā)、重試、日志、打印機狀態(tài)回讀。我一般會在 Delphi 里封一個TLabelPrinter類把 Bartender 的調用包起來業(yè)務層只調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里面是字段名值的鍵值對循環(huán)塞進模板異常統(tǒng)一記日志返回布爾值讓業(yè)務層知道成功與否。Copies默認 1需要多份就傳。5.2 打印狀態(tài)回讀和重試Bartender 的PrintOut異步模式下你只知道指令發(fā)出去了不知道打印機有沒有真的打。要確認狀態(tài)得拿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表示成功其他值對應錯誤、取消、缺紙等。重試邏輯就圍繞這個狀態(tài)寫如果狀態(tài)不是完成等兩秒再試一次最多三次。注意阻塞模式下別在主線程里調會卡界面放到線程里或者用TThread.Queue異步處理。5.3 一個我常用的調試習慣每次改完模板或者打印邏輯我不會直接上產線而是先在 Delphi 里調PrintPreview把預覽窗口彈出來看一眼。確認字段替換、打印機、份數都對了再改成PrintOut正式打。這個習慣幫我省了很多廢標簽紙。另外模板文件我會用 Git 管起來每次改動都有記錄出問題能回滾。Bartender 的.btw文件是二進制Git 不能 diff但至少能看出版本變化。從那以后我每次接新的 Bartender 調用需求都強制走一遍先導入類型庫編譯通過再寫最小打印 Demo確認字段和打印機都對最后才往業(yè)務代碼里集成。希望幫到你。本文還有配套的精品資源點擊獲取