話說筆者的同事知道 XE7 可以銜接 IPCam、iBeacon、Bluetooth Printer 等設備後,很積極的自動幫筆者跟廠商借了 Bluetooth Printer,筆者心裡想說,筆者只說說而已,又還沒有客戶說要這樣用,怎麼...,但借了都借了,總不好意思說接不起來吧,只好燃燒自己的黑頭髮想辦法把 solution 給弄出來。

這次借到的印表機,總共有兩台,分別是 BIXOLON SPP-R200IIBIXOLON SPP-R300,都是熱感應的移動式藍芽印表機,只是支援的紙張大小不同,其他部分大同小異。

BIXOLON 官方網站有提供 android 的 SDK,目前最後一版是 2.2.0

下載後解壓縮,可以在 libs 目錄裡取得 BixolonPrinter.jar,另外在 doc 目錄中則有英文版的 API 使用手冊(Manual_POS_Printer SDK for Android API Reference Guide_english_Rev_1_05.pdf)

利用上一篇文章提到的方式,將 BixolonPrinter.jar 匯出成 .pas 的檔案,挖挖,這次可不得了,總共匯出 70 個 com.bixolon.printer.xxx.pas 的檔案,每個檔案代表一個 java 的 class。

可別被這個大陣仗給嚇到了,要能順利 uses 這些 unit,你必須要有完整的 android API wrapper 成 pascal 的檔案,所幸已經有好心人幫我們準備好嚕...

FMXExpress - android-object-pascal-wrapper

下載完成後,解壓縮,選擇你想要用的 API Level 版本(筆者是選 android-15,相當於 androd 4.0.3),把該路徑加入 delphi options 中的 Library Path

然後就新增一個 app 專案,先加入 uses com.bixolon.printer.BixolonPrinter 這個 unit,編譯看看,應該會遇到非常多的 Circular unit reference (循環參照) 的問題,像下圖

這些循環參照的問題,不能說 jar to pas 的工具有問題,這些工具只是忠實的把每個 java class 匯出成一個 .pas unit,不會考慮是否有循環參照的狀況(maybe java 世界沒有此限制?),所以這部分必須手動自行調整。也許 EMBT 官方後面推出的 Java2OP 會一併把這部分解決?

如果上述這些都沒問題後,那麼就開始撰寫簡單的列印測試程式吧

參考畫面如下

不囉唆,直接 post 出完整程式碼

unit uMain;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  Androidapi.JNIBridge, Androidapi.JNI.os, Androidapi.JNI.JavaTypes,
  com.bixolon.printer.BixolonPrinter,
  android.bluetooth.BluetoothAdapter,
  android.bluetooth.BluetoothDevice,
  FMX.Platform.Android,
  Androidapi.Helpers, FMX.Layouts, FMX.Memo, FMX.ListBox;

type
  TForm1 = class(TForm)
    btnConnectDevice: TButton;
    Memo1: TMemo;
    btnListDevice: TButton;
    ListBox1: TListBox;
    btnPrintText: TButton;
    Layout1: TLayout;
    btnPrintQRCode: TButton;
    procedure btnPrintClick(Sender: TObject);
    procedure btnPrintQRCodeClick(Sender: TObject);
    procedure btnListDeviceClick(Sender: TObject);
    procedure btnConnectDeviceClick(Sender: TObject);
  private
    { Private declarations }
    type
      TMyLEScanCallback = class(TJavaLocal, JHandler_Callback)
      private
        [weak] FParent: TForm1;
      public
        constructor Create(AParent: TForm1);
        function handleMessage(msg: JMessage): Boolean; cdecl;
      end;
  strict private
    FMyLEScanCallback : TMyLEScanCallback;
    FHandler : JHandler;
    FLooper : JLooper;
    FBixolonPrinter : JBixolonPrinter;

    function CheckBluetooth : Boolean;
    procedure getBonded(const aSL :TStrings);

    procedure ConnectDefaultDevice;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}
{$R *.NmXhdpiPh.fmx ANDROID}

{ TForm1.TMyLEScanCallback }

constructor TForm1.TMyLEScanCallback.Create(AParent: TForm1);
begin
  FParent := AParent;
end;

function TForm1.TMyLEScanCallback.handleMessage(msg: JMessage): Boolean;
begin
  //
end;

procedure TForm1.btnConnectDeviceClick(Sender: TObject);
begin
  FMyLEScanCallback := TMyLEScanCallback.Create(self);

  FHandler := TJHandler.JavaClass.init(TJLooper.JavaClass.getMainLooper, FMyLEScanCallback);

  FBixolonPrinter := TJBixolonPrinter.JavaClass.init(MainActivity, FHandler, TJLooper.JavaClass.getMainLooper);

  if ListBox1.Items.Count = 0 then Exit;

  if ListBox1.ItemIndex = -1 then
  begin
    FBixolonPrinter.connect(StringToJString(ListBox1.Items[0]));
    Memo1.Lines.Add('connect ' + ListBox1.Items[0]);
  end
  else
  begin
    FBixolonPrinter.connect(StringToJString(ListBox1.Items[ListBox1.ItemIndex]));
    Memo1.Lines.Add('connect ' + ListBox1.Items[ListBox1.ItemIndex]);
  end;
end;

procedure TForm1.btnListDeviceClick(Sender: TObject);
var
  lSL : TStringList;
begin
  if not CheckBluetooth then exit;

  getBonded(Memo1.Lines);
end;

procedure TForm1.btnPrintClick(Sender: TObject);
begin
  ConnectDefaultDevice;

  if FBixolonPrinter = nil then
  begin
    Showmessage('請先確認要連線的印表機');
    Exit;
  end;

  FBixolonPrinter.printText(StringToJString('Hello, Delphi XE7' + #10),
    TJBixolonPrinter.JavaClass.ALIGNMENT_LEFT,
    TJBixolonPrinter.JavaClass.TEXT_ATTRIBUTE_FONT_A,
    TJBixolonPrinter.JavaClass.TEXT_SIZE_HORIZONTAL1,
    False);
end;

procedure TForm1.btnPrintQRCodeClick(Sender: TObject);
var
  lData : string;
begin
  ConnectDefaultDevice;

  if FBixolonPrinter = nil then
  begin
    Showmessage('請先確認要連線的印表機');
    Exit;
  end;

  lData := 'http://www.winton.com.tw';
  FBixolonPrinter.printQrCode(StringToJString(lData),
    TJBixolonPrinter.JavaClass.ALIGNMENT_CENTER,
    TJBixolonPrinter.JavaClass.QR_CODE_MODEL2,
    //size
    8,
    False);
end;

function TForm1.CheckBluetooth: Boolean;
var
  x : JBluetoothAdapter;
  s : String;
begin
  //確認藍芽設備
  Result := False;
  x := TJBluetoothAdapter.JavaClass.getDefaultAdapter;

  if x = nil then Exit;

  s := jstringtostring(x.getName);

  Result := x.isEnabled;
end;

procedure TForm1.ConnectDefaultDevice;
begin
  //連接預設的藍芽設備
  if FBixolonPrinter <> nil then Exit;

  btnListDeviceClick(nil);

  btnConnectDeviceClick(nil);
  Sleep(1000);
end;

procedure TForm1.getBonded(const aSL : TStrings);
var
  x:JBluetoothAdapter;
  externalDevices:JSet;
  bonded:Tjavaobjectarray<Jobject>;
  it:Jiterator;
  o:JBluetoothDevice;
begin
  //列出有哪些藍芽設備,並將 address 加入到 Listbox1

  x:=TJBluetoothAdapter.JavaClass.getDefaultAdapter;
  externalDevices:=x.getBondedDevices;

  it:=externalDevices.iterator;

  while it.hasNext do
  begin
    o:=TJBluetoothDevice.Wrap((it.next as ILocalObject).GetObjectID);
    aSL.Add(jstringtostring(o.getName)+'='+jstringtostring(o.getAddress));
    if jstringtostring(o.getAddress) <> '' then
      ListBox1.Items.Add(jstringtostring(o.getAddress));
  end;
end;

end.

有圖有真相!