打印机池的设计与实现
先说一个应用场景——中厨。
服务员先在点菜电脑上点菜,点好的菜单信息要在各出品部的“出品印机”上打印出来,各出品部的师傅再按照菜单来做菜。
不同的菜,要在不同的出品打印机上打印,有的菜要在多个出品打印机上打印。
整个中厨大概有十来个出品部门,有的出品部门有一台出品打印机,有的出口部门有几台。
笔者把这些打印机统称为“打印机池”。
// 单元功用:打印相关
// 单元设计:cxg
// 设计日期:2014-05-12
// 每一台打印机都有自己的任务队列和处理任务队列的线程
// 快餐只有一个出品部门
unit untPrintTask;
interface
uses
System.SysUtils, System.Classes,
Datasnap.DBClient, frxclass,
System.Generics.Collections;
type
TBillContent = record // 小票内容
machineNo: string; // POS机号
skyName: string; // 收款员姓名
saleNo: string; // 小票号
saleTime: TDateTime; // 销售时间
amount: Currency; // 应收
pay: Currency; // 支付
change: Currency; // 找零
prnData: OleVariant; // 小票明细:商品名称、单价、数量、金额。。。。。。
deskNo:string; // 台号
payType: string; // 支付方式
end;
type
TPrinterInfo = record // 打印机信息
prnNo: Integer; // 印机编号
prnName: string; // 印机名称
prnType: string; // 结账、厨打
prnWidth: Integer; // 50mm\76mm\80mm
remark: string; // 备注
prnModel: string; // 打印模版
end;
type
TOneTimePrint = record // 一次打印
printerInfo: TPrinterInfo; // 打印机信息
billContent: TBillContent; // 小票内容
end;
type
TPrintTaskThread = class(TThread) // 打印任务线程
private
FPrintQueue: TQueue<TOneTimePrint>; // 打印队列
protected
procedure Execute; override;
public
constructor Create; overload;
destructor Destroy; override;
property PrintQueue: TQueue<TOneTimePrint> read FPrintQueue
write FPrintQueue;
end;
var
g_PrintTasks: TDictionary<string, TPrintTaskThread>; // <打印机名字, TPrintTask>
implementation
{ TPrintTask }
uses untFastReport, UntSysConst;
constructor TPrintTaskThread.Create;
begin
Create(False);
FreeOnTerminate := False;
// 创建打印队列
FPrintQueue := TQueue<TOneTimePrint>.Create;
end;
destructor TPrintTaskThread.Destroy;
begin
// 释放打印队列
FreeAndNil(FPrintQueue);
inherited;
end;
procedure TPrintTaskThread.Execute;
var
OneTimePrint: TOneTimePrint;
dm: TdmFastReport;
c: TfrxComponent;
begin
while not Self.Terminated do
begin
if Assigned(FPrintQueue) and (FPrintQueue.Count > 0) then
begin
// 从任务队列中提取一个任务
OneTimePrint := FPrintQueue.Dequeue;
dm := TdmFastReport.Create(nil);
try
try
// 小票明细数据
dm.cds.Data := OneTimePrint.billContent.prnData;
// 小票模板
dm.report.LoadFromFile(OneTimePrint.printerInfo.prnModel);
// 哪个打印机
dm.report.PrintOptions.Printer := OneTimePrint.printerInfo.prnName;
// 变量赋值
c:=dm.report.FindObject('mmShopName');
if c<>nil then
TfrxMemoView(c).Memo.Text := UserInfo.ShopName;
c := dm.report.FindObject('mmMachineNo');
if c<>nil then
TfrxMemoView(c).Memo.Text := OneTimePrint.billContent.machineNo;
c:=dm.report.FindObject('mmSKY');
if c<>nil then
TfrxMemoView(c).Memo.Text := OneTimePrint.billContent.skyName;
c:= dm.report.FindObject('mmBillNo');
if c<>nil then
TfrxMemoView(c).Memo.Text := OneTimePrint.billContent.saleNo;
c:=dm.report.FindObject('mmSaleTime');
if c<>nil then
TfrxMemoView(c).Memo.Text := FormatDateTime('yyyy-mm-dd hh:nn',OneTimePrint.billContent.saleTime);
c:= dm.report.FindObject('mmDeskNo');
if c<>nil then
TfrxMemoView(c).Memo.Text := OneTimePrint.billContent.deskNo; // 台号
c:= dm.report.FindObject('mmPayType');
if c<>nil then
TfrxMemoView(c).Memo.Text := OneTimePrint.billContent.payType;
c:= dm.report.FindObject('mmAmount');
if c<> nil then
TfrxMemoView(c).Memo.Text := FormatCurr('0.00', OneTimePrint.billContent.amount);
c:=dm.report.FindObject('mmPay');
if c<>nil then
TfrxMemoView(c).Memo.Text := FormatCurr('0.00', OneTimePrint.billContent.pay);
c:= dm.report.FindObject('mmGiveChange');
if c<> nil then
TfrxMemoView(c).Memo.Text := FormatCurr('0.00', OneTimePrint.billContent.change);
// 开始打印
dm.report.PrepareReport();
dm.report.Print;
except
// 打印失败,重新加入任务队列
Self.FPrintQueue.Enqueue(OneTimePrint);
end;
finally
FreeAndNil(dm);
end;
end;
// 线程休眠
Sleep(1000);
end;
end;
end.
procedure TfrmSettleAccount.PrintBill;
var
OneTimePrint: TOneTimePrint;
p: TPrintTaskThread;
begin
// 结帐打印机和出品打印机都打印相同的模板
frmPos.cdsPrinter.First;
while not frmPos.cdsPrinter.Eof do
begin
// 打印机信息
OneTimePrint.printerInfo.prnNo := frmPos.cdsPrinter.FieldByName('prnNo')
.AsInteger;
OneTimePrint.printerInfo.prnName := frmPos.cdsPrinter.FieldByName
('prnName').Text;
OneTimePrint.printerInfo.prnType := frmPos.cdsPrinter.FieldByName
('prnType').Text;
OneTimePrint.printerInfo.prnWidth := UserInfo.PaperWidth;
OneTimePrint.printerInfo.remark := frmPos.cdsPrinter.FieldByName
('remark').Text;
// 不同宽度的打印机加载不同的打印模板
case OneTimePrint.printerInfo.prnWidth of
58:
OneTimePrint.printerInfo.prnModel :=
ExtractFilePath(Application.ExeName) + 'report\pos58.fr3';
76:
OneTimePrint.printerInfo.prnModel :=
ExtractFilePath(Application.ExeName) + 'report\pos76.fr3';
80:
OneTimePrint.printerInfo.prnModel :=
ExtractFilePath(Application.ExeName) + 'report\pos80.fr3';
end;
// 小票内容
OneTimePrint.billContent.machineNo := UserInfo.MachineId;
OneTimePrint.billContent.skyName := UserInfo.UserName;
OneTimePrint.billContent.saleNo := UserInfo.saleNo;
OneTimePrint.billContent.saleTime := Now;
OneTimePrint.billContent.amount := self.Total.totalAmount;
OneTimePrint.billContent.pay := self.Total.totalAmount;
OneTimePrint.billContent.Change := StrToCurr(edtChange.Text);
OneTimePrint.billContent.prnData := frmPos.cdsSaleD.Data;
OneTimePrint.billContent.payType := frmPos.cdsSaleM.FieldByName
('payType').Text;
OneTimePrint.billContent.deskNo := frmPos.edtDeskNo.Text;
// 加入打印队列
if g_PrintTasks.TryGetValue(frmPos.cdsPrinter.FieldByName('prnName').Text, p)
then
begin
p.PrintQueue.Enqueue(OneTimePrint);
end;
frmPos.cdsPrinter.Next;
end;
end;