标签打印技术

BarTender是一款优秀的条形码打印软件,可以支持很多种类型的条形码设计和打印,具体大家可参考他的官网(Barcode and Label Making Software | BarTender),这里不多介绍。

BarTender的几种集成模式。ActiveX COM集成,Commander的触发式集成,还有9.0版本后支持的.NET SDK的支持。简单说说这几个方式:

1 ActiveX COM:BarTender在你的机器上安装好后,会在机器上注册一个COM,你在程序中可以调用这个COM,使用他提供的方法和属性,进行BarTender的程序级控制。这个集成方式比较实用,对BarTender的版本要求低,不过因为是基本的COM使用,所以要求你对COM的接口比较熟悉,对整个BarTender的调用流程和细节比较清晰,对于COM接口的描述,大家可以参考Automation.chm这个帮助文档(在SeagullSuite)。

2 Commander的触发式集成:简单来说,这个是通过配置BarTender,让他监视某个文件,当程序生成这个文件的时候,就会触发唤醒BarTender进行打印工作,这种方式我觉得不大好用,所以了解也不多,欢迎讨论交流。

3 .NET SDK:这个是9.0版本开始支持的一个适用于.NET框架的SDK,里面提供了很多类来进行BarTender的控制,由于这个SDK也是用.NET编写的,所以对于用在.NET下的集成非常方便,不过由于对版本有要求,所以要考虑客户的成本来使用。

NET SDK

.NET SDK里面分开了两个,一个是标准的,一个是Server版的,标准的只是简单的开启BarTender进程去处理打印任务,所以当有多个任务同时打印的时候,就需要自己去管理任务队列问题;而Server版的就是里面有了任务队列机制,很方便的管理任务队列和BarTender的进程资源。但是Server版需要安装BarTender的Enterprise版才支持。

标准版

SDK其实底层上也是对COM的调用,只不过封装了,让开发者更好操作,标准的SDK应该看帮助文档没什么问题,里面对各个类以及他们的方法和属性都做了详细的解释。

下面给出一段代码,里面代表了最简单的典型使用 首先添加引用,在.NET的TAB里面可以找到Seagull.BarTender.Print

using Seagull.BarTender.Print;

//new an BarTender engine

Engine engine = new Engine(true);

//use the engine to open a format document to return a LabelFormatDocument object

LabelFormatDocument format = engine.Documents.Open("c:\\test.btw");

//use the LabelFormatDocument object to print

format.Print("Select printer", out messages);

服务版

对于Server版的SDK,我在这里稍微的描述一下他的队列管理详细吧,不过单纯使用SDK是不需要知道这些东西的,使用上跟标准的SDK一样简单,只要参考帮助文档和Samples就可以很好明白了。写出他的队列管理详细仅供大家探讨~~大家看这一部分源码的时候,要先看一下线程的相关知识。

1) 程序流程核心是:把任务放进任务队列里,任务队列检测到有任务在排队的话,就把任务扔到引擎管理中,选择一个空闲的引擎来执行任务。

2) 管理任务队列主要是使用BtPool类,这个类里面的Consume函数不断地检测队列是否为空,如果不为空,则开始任务的提交工作,先通过engine.Status != TaskEngineStatus.Busy来检测空闲的Engine,然后通过engine.SubmitTask(this.m_masterTaskQueue)来提交任务到Engine里。

在SubmitTask函数里面,我们可以看到把扔进来的任务付给当前引擎对应的任务的句子:this.m_task = tasks.Dequeue();

然后我们查看管理引擎的主要类TaskEngine,可以看到里面同样有一个Consume函数,他会不断检测当前引擎对应的任务是否为空,不为空则开始执行任务。

因此,当SubmitTask函数里通过this.m_task = tasks.Dequeue()把任务赋给了当前引擎的任务后,就会开始任务执行的Task.Run()方法。

在Task.Run()方法里,根据多态的特性,调用this.OnRun()的时候,是调用了PrintLabelFormatTask类的OnRun()方法。在PrintLabelFormatTask类的OnRun()方法里就实现了把任务发送到打印机或者打印成文件的工作。

BtPool管理任务队列和引擎池。TaskEngine对应的是一个引擎。TaskQueue为队列的相关操作的类。TaskEngines为TaskEngine的相关操作的类。

网络打印

大家可能看到网络浏览器的打印例子,里面写到了客户端的打印,这个客户端的打印其实也是在服务器上做,但服务器上做的只是把打印的结果输出为一个打印文件,然后把文件传送到客户端,客户端再通过JaveScript调用本地的打印机读取打印文件进行打印。这个要求服务器和客户端都装有同一个打印机驱动,而且客户端真正打印时使用的打印机驱动,要跟服务器上用来生成打印文件的打印机驱动一致。这里也有对这种方式的一些资料,仅供参考:

1)WebLabelPrint客户端打印

//设定打印到文件

labelFormat.PrintSetup.PrintToFile = true;

//设定打印机

labelFormat.PrintSetup.PrinterName = compatibleServerPrinter;

//设定打印机的License

labelFormat.PrintSetup.PrintToFileLicense = Request.Form.Get(_listPrinters.PrintLicenseUniqueID);

string tempFullPath = (string)Application["TempFolderFullPath"];

//设定打印文件的路径

labelFormat.PrintSetup.PrintToFileName = System.IO.Path.Combine(tempFullPath,Guid.NewGuid().ToString() + ".prn");

2)设定好以上东西后,就像正常的服务器打印一样,把任务发送到任务队列,当打印完成的时候,调用函数TaskPrint_Succeeded,里面将printTask.PrintCode读取出来,然后把打印码发送到本地的打印机进行打印。

3)ClientPrinting.js: PrintList.aspx页面里用到GetClientPrinters函数,完成客户端打印的主要是BarTenderPrintClient.js和WebLabelPrintSample.js文件。

4)BarTenderPrintClient.js文件里主要包含了一些针对打印机的操作,如获得打印机列表,匹配服务器和客户端的打印机,获得打印机的License,还有就是把打印码发送到对应的打印机上。

5)WebLabelPrintSample.js文件主要包含了将客户端打印机列表填充到DropDownList里(PrintListControl.ascx),将客户端打印机列表填充到Table里(Printers.aspx)

ActiveX COM

COM里面主要的就那么几个类,下面一一详细说明

1) Application类:实际上代表一个BarTender的进程,当你新建一个此类的对象的时候,就会出现一个新的BarTender进程。

2) Format类:实际上代表一个格式标签(BTW文件),使用Application. Formats.Open()方法可以打开并返回一个Format对象。这个类里面包含了打印设置,标签的参数设置等一系列的设置,很重要的一个类。

3) NamedSubString类:管理标签文件里所有SubString的类。

4) DataBase类:管理标签文件里设置的所有数据库链接的类。

5) QueryPromts类:管理标签文件里面数据库查询参数的类。

鉴于Format类是比较重要的类,这里再对他的相关属性和方法也做一下说明:

1) Print方法:这个就是最常用的打印方法,里面可设置打印的任务名,是否等待打印完成,等待超时时间,打印过程输出的信息。

2) PrintOut方法:如果你需要在打印时出现打印设置对话框和状态框,你可以选择这个方法来实现。

3) Save方法:保存对Format的更改。

4) SetNamedSubStringValue方法:设置某个特定的SubString的值,这里就可以作为一个动态改变打印内容的方法。

5) SetPromt方法:跟SetNamedSubStringValue方法类似,不过他设置的是打印提示的值,某些标签通过设置可以在打印的时候弹出对话框,输入某些变量的值来改变打印内容,这个方法就是动态设置这些变量的。不过在集成中比较少用。

6) IdenticalCopiesOfLabel属性:这个是设置打印时要打印多少份相同的标签的,默认为标签设置。

7) NumberSerializedLabels属性:这个是序列化打印时使用的,当你的标签启动了序列化后,这个属性代表的就是打印的份数,譬如你的序列化初始数据是1,增量为1,NumberSerializedLabels设置为5,那么就会打印出1、2、3、4、5,五个标签出来。

8) Printer属性:指定要使用的打印机,默认为系统指定的默认打印机。

9) PrintToFile属性:标示是马上用打印机打印出实物,还是生成一个打印文件。

动态控制打印内容的方式一种是直接通过SetNamedSubStringValue方法去设置某些变量的值,然后进行打印,这个适合小量的打印,因为每次都要调用Application和Format对象来处理,所以打印100份就要调用一百次,效率低,但是可以灵活设置每次的打印内容;

另外一种是使用序列化打印,这个的效率就高了,因为他是只调用一次把数据扔给BarTender,之后的都是BarTender根据序列化规则去生成打印内容,不过这个方式就欠缺灵活性,只适用于标签的内容是有一定的规律进行变化的大批量打印。

第三种动态控制打印内容的方法是使用DataBase和QueryPromts类,主要思路是,在标签里设置好各项内容,这些内容都是对应数据库的某些字段,然后设置好查询条件,暴露一个或多个查询参数,然后在程序里通过QueryPromts类来设置这些查询参数来查询出不同的数据库内容,达到动态改变打印内容的目的。Format.Databases.QueryPrompts.GetQueryPrompt()可以特定的查询条件,然后通过设置Value属性来设置他的值,最后设置完所有查询条件后进行打印。

二次开发示例

二次开发须引用UnityEngine.dll 和 Seagull.BarTender.Print.dll 这两个动态链接库文件。

并且软件还要以.NETFramework 2.0 兼容方式运行,*Demo.exe.config 文件配置如下:

<?xml version="1.0"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">

<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client"/></startup>
</configuration>

二次开发核心代码如下:

using Seagull.BarTender.Print;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Printing;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace BarTender_Dev_Dome
{
public partial class PrintForm : Form
{
private string _bmp_path = Application.StartupPath + @"\exp.jpg";
private string _btw_path = "";
string _PrinterName = "";
public PrintForm()
{
InitializeComponent();
}

private void PrintForm_Load(object sender, EventArgs e)
{
Printers printers = new Printers();
foreach (Printer printer in printers)
{
printer_comboBox.Items.Add(printer.PrinterName);
}

if (printers.Count > 0)
{
// Automatically select the default printer.
printer_comboBox.SelectedItem = printers.Default.PrinterName;
}
}

private void openFilebtn_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Multiselect = false;//多个文件
dialog.Title = "请选择要烧录的文件";
dialog.Filter = "bwt文件(*.btw)|*.btw";
dialog.InitialDirectory = Application.StartupPath;

if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
_btw_path = dialog.FileName;
fileNametBox.Text = dialog.SafeFileName;
fileNametBox.BackColor = Color.LightGreen;

pictureBox.Image = null;
using (Engine btEngine = new Engine(true))
{
LabelFormatDocument labelFormat = btEngine.Documents.Open(_btw_path);

if (labelFormat != null)
{
Seagull.BarTender.Print.Messages m;
labelFormat.ExportPrintPreviewToFile(Application.StartupPath, @"\exp.bmp", ImageType.JPEG, Seagull.BarTender.Print.ColorDepth.ColorDepth24bit, new Resolution(300,300), System.Drawing.Color.White, OverwriteOptions.Overwrite, true, true, out m);
labelFormat.ExportImageToFile(_bmp_path, ImageType.JPEG, Seagull.BarTender.Print.ColorDepth.ColorDepth24bit, new Resolution(300, 300), OverwriteOptions.Overwrite);

Image image = Image.FromFile(_bmp_path);
Bitmap NmpImage = new Bitmap(image);
pictureBox.Image = NmpImage;
image.Dispose();
}
else
{
MessageBox.Show("生成图片错误", "操作提示");
}
}
}
}

private void printer_comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
_PrinterName = printer_comboBox.Text;
}

void PrintBar(bool isPreView = false)
{
if (_btw_path.Length < 5)
{
fileNametBox.BackColor = Color.Red;
return;
}
using (Engine btEngine = new Engine(true))
{
LabelFormatDocument labelFormat = btEngine.Documents.Open(_btw_path);

try
{
labelFormat.SubStrings.SetSubString("name", name_textBox.Text);
labelFormat.SubStrings.SetSubString("age", age_textBox.Text);
labelFormat.SubStrings.SetSubString("ID", id_textBox.Text);
labelFormat.SubStrings.SetSubString("code", num_textBox.Text);
}

catch (Exception ex)
{
MessageBox.Show("修改内容出错 " + ex.Message, "操作提示");
}

if (labelFormat != null)
{
//Generate a thumbnail for it.
labelFormat.ExportImageToFile(_bmp_path, ImageType.BMP, Seagull.BarTender.Print.ColorDepth.ColorDepth24bit, new Resolution(407, 407
), OverwriteOptions.Overwrite);

System.Drawing.Image image = System.Drawing.Image.FromFile(_bmp_path);
Bitmap NmpImage = new Bitmap(image);
pictureBox.Image = NmpImage;
image.Dispose();
}
else
{
MessageBox.Show("生成图片错误", "操作提示");
}

if (isPreView) return;

if (_PrinterName != "")
{
labelFormat.PrintSetup.PrinterName = _PrinterName;
labelFormat.Print("BarPrint" + DateTime.Now, 3 * 1000);
}
else
{
MessageBox.Show("请先选择打印机", "操作提示");
}
}
}

private void print_btn_Click(object sender, EventArgs e)
{
PrintBar();
}

private void preview_btn_Click(object sender, EventArgs e)
{
PrintBar(true);
}
}
}

ZPL语言

首先下载模板到打印机

^XA
^DFE:FORMAT^FS                  // 下载保存模板
^LH0,0                       //原点
^FO12,121^GB643,0,1^FS             // 分割线
^FO12,173^GB643,0,1^FS
^CI26                       //ASCII Transparency和多字节亚洲编码
^SEE:GB18030.DAT                //码表
^CW1,E:SIMSUN.FNT                //字体(宋体)
^FO300,45^A1N,25,25^CI26^FD打印人:^FS    //其中A1N 表示使用标号为1的字体,后面的25,25 代表字体大小
^FO300,89^A1N,25,25^CI26^FD打印日期:^FS
^FO27,143^A1N,25,25^CI26^FD门店信息:^FS
^FO43,253^A1N,25,25^CI26^FD起始巷道:^FS
^FO43,199^A1N,25,25^CI26^FD订单编号:^FS
^FO18,40^A1N,33,25^FN1^FS
^FO39,102^A1N,35,35^FN2^FS
^FO420,45^A1N,33,33^FN3^FS
^FO149,148^A1N,30,30^FN4^FS
^FO356,148^A1N,30,30^FN5^FS
^BY2,3,120^FT37,429^BCN,,Y,N,,A
^FN6^FS
^FO210,254^A1N,28,28^FN7^FS
^FO210,200^A1N,28,28^FN8^FS
^FO420,200^A1N,28,28^FN9^FS
^FO420,93^A1N,28,25^FN10^FS
^PQ1,,,Y
^XZ

得到变量后再次打印

^XA
^XFE:FORMAT^FS
^FN1^FD首打^FS
^FN2^FD箱拣标签^FS
^FN3^FD吕笑笑^FS
^FN4^FD20126^FS
^FN5^FD京东2号店^FS
^FN6^FD202010291437001^FS
^FN7^FDSRM03^FS
^FN8^FD20201029001^FS
^FN9^FD2/9-36/60^FS
^FN10^FD2020-10-29 16:12^FS
^XZ

ZPL语法

^XA

^JMA^LL200^PW680^MD10^PR2^PON^LRN^LH0,0

^FO10,30
^A0N,72,72
^FD Hello World!^FS

^XZ

^FD Hello World!^FS FD是一段字符串的开头,FS是一段字符串的结尾,string就是代表要打印的字符串 ^FOx,y x代表横坐标,y代表纵坐标,如果你将x的值改为20,那么“ Hello World!”将会右移一段距离。 ^Aab,c,d

  • a,字体类型,的取值范围从【0-9,A-Z】,0是默认的内置字体,若需要其他字体则需要设置,在打印中文这一节将会说明。
  • b,旋转角度,注意参数a和b之间并没有逗号,有【N,R,I,B】四个选项,分别代表正常,顺时针旋转90°,180°、270°。
  • c,字符高度
  • d,字符宽度

^JMA 每毫米设定点,可选参数【A,B】,A表示【24 dots/mm, 12 dots/mm, 8 dots/mm or 6 dots/mm】,B表示【12 dots/mm, 6 dots/mm, 4 dots/mm or 3 dots/mm】,默认A ^LL200 标签高度,这里是20mm ^PW680 标签宽度,这里是68mm ^MD10 标签深度,可选值【-30~30】,值越高标签浓度越高 ^PR2 打印速度,实际这是一个多参数的指令,^PRa,b,c,b和c不设置则为默认值。

其中a是打印速度,可选值【1-14,A-E】,值越大速度越快,其中字母【2=A,3=B,4=C,6=D,8=E】,即设置A与设置2无异。

b和c的参数用于设置推出和回卷速度,默认即可。 > ^PON 打印方向,有【N,I】两个值可选,N是正常,I是倒置(标签底部先出) > ^LRN 打印反转,有【Y,N】两个值可选,N是正常,Y表示将产生黑底白字(需要先绘制黑色填充方框)

^XA
^SEE:GB18030.DAT^FS
^CWZ,E:SIMSUN.FNT
^CI26
^JMA^LL200^PW680^MD10^RP2^PON^LRN^LH0,0

^FO20,100
^AZN,72,72
^FD中123文ABC测试^FS

^PQ1
^XZ

^SEa:b.c 参数a代表本地编码表的选择,可选值有【R,E,B,A】这四个参数的值定义暂时没有详细的资料,目前知道它用于区分ZPL指令和ZPL II指令,默认为R,这里使用E。

参数b代表编码的名称,目前网上找到的资料都是使用GB18030,暂时没有发现其他编码。

参数c代表编码的后缀名,一般是DAT > ^CWa,b:c.d 参数a代表设置的这个字体编号,可选值【A-Z和0-9】,当这里设置了以后,^A的第一个参数才能引用到这个字体。 参数b同^SE的参数a。 参数c代表字体名称,这里的SIMSUN是宋体 > ^AZN,72,72 注意第一个参数Z,它代表使用我们自己设置的Z字体,72代表字体的大小,由于使用了点阵字体,这里的大小必须是24的整数倍。