欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于Win服务的标签打印(模板套打)

程序员文章站 2022-07-04 22:46:28
最近做了几个项目,都有在产品贴标的需求 基本就是有个证卡类打印机,然后把产品的信息打印在标签上。 然后通过机器人把标签贴到产品上面 标签信息包括文本,二维码,条形码之类的,要根据对应的数据生成二维码,条形码。 打印标签的需求接到手后,开始了我的填坑之旅。 打印3.0源代码:https://githu ......

最近做了几个项目,都有在产品贴标的需求

基本就是有个证卡类打印机,然后把产品的信息打印在标签上。

然后通过机器人把标签贴到产品上面

标签信息包括文本,二维码,条形码之类的,要根据对应的数据生成二维码,条形码。

打印标签的需求接到手后,开始了我的填坑之旅。

 

打印3.0源代码:https://github.com/zeqp/zeqp.print

 

打印1.0

第一个项目开始,因为原来没有研究过打印,所以在bing上查了一下.net打印机关的资料

发现基本上都是基于.net的
system.drawing.printing.printdocument
这个类来做自定义打印

大家都用这个做打印,我想按理也没有问题。

所以开始了我的代码。

printdocument去做打印,无非就是设置好打印机名称,
defaultpagesettings.printersettings.printername
打印份数
defaultpagesettings.printersettings.copies
纸张方向
defaultpagesettings.landscape
然后打印的具体的信息就是事件printpage写进去
然后调用
graphics.drawstring,graphics.drawimage来写入具体的文本与图片
graphics.draw的时候要指定字体,颜色,位置等数据
我把这些做成配置数据。
然后1.0版本就成了。
基于Win服务的标签打印(模板套打)

 

 


下图为位置的配置文件
基于Win服务的标签打印(模板套打)

 

 

  代码一写完,用vs调试的时候。跑得飞起。、

所有的字体,要打印数据的位置也通过配置文件可以动态的调整。感觉还算完美。
但是现实很骨感,马上就拍拍打脸了


printdocument类只能以winform的方式运行,不能以服务的方式运行。
具体可以参考:https://docs.microsoft.com/zh-cn/dotnet/api/system.drawing.printing?redirectedfrom=msdn&view=netframework-4.8

基于Win服务的标签打印(模板套打)

 

 

 幸好客户方面没有什么要求,而且生产的时候会有一台专门的上位机可以做这个事,所以做了一个*面的winform。在电脑启动的时候运行

从而解决了不能以服务的方式运行的问题。

 

打印2.0

做完打印1.0后,又接到了一个项目。又是有打印相关的功能,自然又分配到我这里来了。

但是对于上一个版本的打印。不能做为服务运行,做为自己写的一个程序,居然有这么大的瑕疵。总感觉心里不爽

想去解决这个问题,但是在bing上找到.net的所有打印都是这样做的。也找不到什么更好的方法。

只到问了很多相关的相关人士。最后给了我一个第三方的商业解决方案bartender
相关参考:https://www.bartendersoftware.com/
这个有自己的模板编辑器,
基于Win服务的标签打印(模板套打)

 

 

 

有自己的sdk,有编辑器,功能也非学强大。不愧是商业打印解决方案。

根据他的sdk,同时安装了相关程序,写下几句打印代码。一个基于win服务的打印出来了

基于Win服务的标签打印(模板套打)

 

 

 于是。打印2.0出来了。

 

打印3.0
但是对于一个基于第三方的商业打印方案,所有功能都是很强大。实现也简单。

就是对于一般公司的小项目。挣的钱还不够买这个商业套件的license

而且对于一个只会使用别人家的sdk的程序。不是一个有灵魂的程序。

因为你都不知道人家背后是怎么实现的。原理是什么都不知道。

对于我,虽然能把这个项目用bartender完成。但是总是对这个打印方案不是很满意。

因为我只在这个上面加了一层壳。不知道后面做了什么。

所以我一直想自己开发一个可以基于win服务运行的打印程序。最好也要有自己的模板编辑器。

只到有一天。无意找到一篇文章

https://docs.aspose.com/display/wordsnet/print+a+document

他这里也解释了有关基于服务的打印有关的问题不能解决。

并且他们已经找到了对应的解决方案。基于他的解决方案。写了对应一个打印帮助类。

这个是基于windows的xps文档api打印。

xps是在win 7后就是windows支持的打印文档类型 类比pdf

基本 xpsprint api   的相关说明

同时基本他的xps打印帮助类。我做了测试。可以完美的在windows服务里面运行关打印。

  1 namespace zeqp.print.framework
  2 {
  3     /// <summary>
  4     /// a utility class that converts a document to xps using aspose.words and then sends to the xpsprint api.
  5     /// </summary>
  6     public class xpsprinthelper
  7     {
  8         /// <summary>
  9         /// no ctor.
 10         /// </summary>
 11         private xpsprinthelper()
 12         {
 13         }
 14 
 15         // exstart:xpsprint_printdocument       
 16         // exsummary:convert an aspose.words document into an xps stream and print.
 17         /// <summary>
 18         /// sends an aspose.words document to a printer using the xpsprint api.
 19         /// </summary>
 20         /// <param name="document"></param>
 21         /// <param name="printername"></param>
 22         /// <param name="jobname">job name. can be null.</param>
 23         /// <param name="iswait">true to wait for the job to complete. false to return immediately after submitting the job.</param>
 24         /// <exception cref="exception">thrown if any error occurs.</exception>
 25         public static void print(string xpsfile, string printername, string jobname, bool iswait)
 26         {
 27             console.writeline("print");
 28             if (!file.exists(xpsfile))
 29                 throw new argumentnullexception("xpsfile");
 30             using (var stream = file.openread(xpsfile))
 31             {
 32                 print(stream, printername, jobname, iswait);
 33             }
 34             //// use aspose.words to convert the document to xps and store in a memory stream.
 35             //file.openread
 36             //memorystream stream = new memorystream();
 37 
 38             //stream.position = 0;
 39             //console.writeline("saved as xps");
 40             //print(stream, printername, jobname, iswait);
 41             console.writeline("after print");
 42         }
 43         // exend:xpsprint_printdocument
 44         // exstart:xpsprint_printstream        
 45         // exsummary:prints an xps document using the xpsprint api.
 46         /// <summary>
 47         /// sends a stream that contains a document in the xps format to a printer using the xpsprint api.
 48         /// has no dependency on aspose.words, can be used in any project.
 49         /// </summary>
 50         /// <param name="stream"></param>
 51         /// <param name="printername"></param>
 52         /// <param name="jobname">job name. can be null.</param>
 53         /// <param name="iswait">true to wait for the job to complete. false to return immediately after submitting the job.</param>
 54         /// <exception cref="exception">thrown if any error occurs.</exception>
 55         public static void print(stream stream, string printername, string jobname, bool iswait)
 56         {
 57             if (stream == null)
 58                 throw new argumentnullexception("stream");
 59             if (printername == null)
 60                 throw new argumentnullexception("printername");
 61 
 62             // create an event that we will wait on until the job is complete.
 63             intptr completionevent = createevent(intptr.zero, true, false, null);
 64             if (completionevent == intptr.zero)
 65                 throw new win32exception();
 66 
 67             //            try
 68             //            {
 69             ixpsprintjob job;
 70             ixpsprintjobstream jobstream;
 71             console.writeline("startjob");
 72             startjob(printername, jobname, completionevent, out job, out jobstream);
 73             console.writeline("done startjob");
 74             console.writeline("start copyjob");
 75             copyjob(stream, job, jobstream);
 76             console.writeline("end copyjob");
 77 
 78             console.writeline("start wait");
 79             if (iswait)
 80             {
 81                 waitforjob(completionevent);
 82                 checkjobstatus(job);
 83             }
 84             console.writeline("end wait");
 85             /*            }
 86                         finally
 87                         {
 88                             if (completionevent != intptr.zero)
 89                                 closehandle(completionevent);
 90                         }
 91             */
 92             if (completionevent != intptr.zero)
 93                 closehandle(completionevent);
 94             console.writeline("close handle");
 95         }
 96         // exend:xpsprint_printstream
 97 
 98         private static void startjob(string printername, string jobname, intptr completionevent, out ixpsprintjob job, out ixpsprintjobstream jobstream)
 99         {
100             int result = startxpsprintjob(printername, jobname, null, intptr.zero, completionevent,
101                 null, 0, out job, out jobstream, intptr.zero);
102             if (result != 0)
103                 throw new win32exception(result);
104         }
105 
106         private static void copyjob(stream stream, ixpsprintjob job, ixpsprintjobstream jobstream)
107         {
108 
109             //            try
110             //            {
111             byte[] buff = new byte[4096];
112             while (true)
113             {
114                 uint read = (uint)stream.read(buff, 0, buff.length);
115                 if (read == 0)
116                     break;
117 
118                 uint written;
119                 jobstream.write(buff, read, out written);
120 
121                 if (read != written)
122                     throw new exception("failed to copy data to the print job stream.");
123             }
124 
125             // indicate that the entire document has been copied.
126             jobstream.close();
127             //            }
128             //            catch (exception)
129             //            {
130             //                // cancel the job if we had any trouble submitting it.
131             //                job.cancel();
132             //                throw;
133             //            }
134         }
135 
136         private static void waitforjob(intptr completionevent)
137         {
138             const int infinite = -1;
139             switch (waitforsingleobject(completionevent, infinite))
140             {
141                 case wait_result.wait_object_0:
142                     // expected result, do nothing.
143                     break;
144                 case wait_result.wait_failed:
145                     throw new win32exception();
146                 default:
147                     throw new exception("unexpected result when waiting for the print job.");
148             }
149         }
150 
151         private static void checkjobstatus(ixpsprintjob job)
152         {
153             xps_job_status jobstatus;
154             job.getjobstatus(out jobstatus);
155             switch (jobstatus.completion)
156             {
157                 case xps_job_completion.xps_job_completed:
158                     // expected result, do nothing.
159                     break;
160                 case xps_job_completion.xps_job_failed:
161                     throw new win32exception(jobstatus.jobstatus);
162                 default:
163                     throw new exception("unexpected print job status.");
164             }
165         }
166 
167         [dllimport("xpsprint.dll", entrypoint = "startxpsprintjob")]
168         private static extern int startxpsprintjob(
169             [marshalas(unmanagedtype.lpwstr)] string printername,
170             [marshalas(unmanagedtype.lpwstr)] string jobname,
171             [marshalas(unmanagedtype.lpwstr)] string outputfilename,
172             intptr progressevent,   // handle
173             intptr completionevent, // handle
174             [marshalas(unmanagedtype.lparray)] byte[] printablepageson,
175             uint32 printablepagesoncount,
176             out ixpsprintjob xpsprintjob,
177             out ixpsprintjobstream documentstream,
178             intptr printticketstream);  // this is actually "out ixpsprintjobstream", but we don't use it and just want to pass null, hence intptr.
179 
180         [dllimport("kernel32.dll", setlasterror = true)]
181         private static extern intptr createevent(intptr lpeventattributes, bool bmanualreset, bool binitialstate, string lpname);
182 
183         [dllimport("kernel32.dll", setlasterror = true, exactspelling = true)]
184         private static extern wait_result waitforsingleobject(intptr handle, int32 milliseconds);
185 
186         [dllimport("kernel32.dll", setlasterror = true)]
187         [return: marshalas(unmanagedtype.bool)]
188         private static extern bool closehandle(intptr hobject);
189     }
190 
191     /// <summary>
192     /// this interface definition is hacked.
193     /// 
194     /// it appears that the iid for ixpsprintjobstream specified in xpsprint.h as 
195     /// midl_interface("7a77dc5f-45d6-4dff-9307-d8cb846347ca") is not correct and the rcw cannot return it.
196     /// but the returned object returns the parent isequentialstream inteface successfully.
197     /// 
198     /// so the hack is that we obtain the isequentialstream interface but work with it as 
199     /// with the ixpsprintjobstream interface. 
200     /// </summary>
201     [guid("0c733a30-2a1c-11ce-ade5-00aa0044773d")]  // this is iid of isequenatialsteam.
202     [interfacetype(cominterfacetype.interfaceisiunknown)]
203     interface ixpsprintjobstream
204     {
205         // isequentualstream methods.
206         void read([marshalas(unmanagedtype.lparray)] byte[] pv, uint cb, out uint pcbread);
207         void write([marshalas(unmanagedtype.lparray)] byte[] pv, uint cb, out uint pcbwritten);
208         // ixpsprintjobstream methods.
209         void close();
210     }
211 
212     [guid("5ab89b06-8194-425f-ab3b-d7a96e350161")]
213     [interfacetype(cominterfacetype.interfaceisiunknown)]
214     interface ixpsprintjob
215     {
216         void cancel();
217         void getjobstatus(out xps_job_status jobstatus);
218     }
219 
220     [structlayout(layoutkind.sequential)]
221     struct xps_job_status
222     {
223         public uint32 jobid;
224         public int32 currentdocument;
225         public int32 currentpage;
226         public int32 currentpagetotal;
227         public xps_job_completion completion;
228         public int32 jobstatus; // uint32
229     };
230 
231     enum xps_job_completion
232     {
233         xps_job_in_progress = 0,
234         xps_job_completed = 1,
235         xps_job_cancelled = 2,
236         xps_job_failed = 3
237     }
238 
239     enum wait_result
240     {
241         wait_object_0 = 0,
242         wait_abandoned = 0x80,
243         wait_timeout = 0x102,
244         wait_failed = -1 // 0xffffffff
245     }
246 }

到此,基于windows服务的打印已经解决。

就只有模板编辑器的事情了。

对于原来做过基于word的邮件合并域的经验。自己开发一个编辑器来说工程量有点大

所以选择了一个现有的,功能又强大的文档编辑器。word来做为我的标签编辑器了。

word可以完美的解决纸张,格式,位置等问题。只是在对应的地方用“文本域”来做占位符

然后用自定义的数据填充就可以了。

下图为word模板编辑
基于Win服务的标签打印(模板套打)

 

 编辑占位符(域)
基于Win服务的标签打印(模板套打)

 

 

这样的话。一个模板就出来了

如果是图片的话。就在域名前加image:

如果是表格的话。在表格的开始加上tablestart:表名
在表格的未尾加上tableend:表名

 

协议的话。走的是所有语言都支持的http,对于以后开发sdk也方便

对于上面的模板,只要发送这样的请球post
基于Win服务的标签打印(模板套打)

对于get请求

基于Win服务的标签打印(模板套打)

 

然后打印出来的效果

基于Win服务的标签打印(模板套打)

到此,打印3.0已经完成。

关键代码
根据请求数据生成打印实体

 1 private printmodel getprintmodel(httplistenerrequest request)
 2         {
 3             var result = new printmodel();
 4             result.printname = configurationmanager.appsettings["printname"];
 5             result.template = path.combine(appdomain.currentdomain.basedirectory, "template", "default.docx");
 6             result.action = printactiontype.print;
 7 
 8             var query = request.url.query;
 9             var dicquery = this.tonamevaluedictionary(query);
10             if (dicquery.containskey("printname")) result.printname = dicquery["printname"];
11             if (dicquery.containskey("copies")) result.copies = int.parse(dicquery["copies"]);
12             if (dicquery.containskey("template"))
13             {
14                 var temppath = path.combine(appdomain.currentdomain.basedirectory, "template", dicquery["template"]);
15                 if (file.exists(temppath))
16                     result.template = temppath;
17             }
18             if (dicquery.containskey("action")) result.action = (printactiontype)enum.parse(typeof(printactiontype), dicquery["action"]);
19 
20             foreach (var item in dicquery)
21             {
22                 if (item.key.startswith("image:"))
23                 {
24                     var keyname = item.key.replace("image:", "");
25                     if (result.imagecontent.containskey(keyname)) continue;
26                     var imagemodel = item.value.toobject<imagecontentmodel>();
27                     result.imagecontent.add(keyname, imagemodel);
28                     continue;
29                 }
30                 if (item.key.startswith("table:"))
31                 {
32                     var keyname = item.key.replace("table:", "");
33                     if (result.tablecontent.containskey(keyname)) continue;
34                     var table = item.value.toobject<datatable>();
35                     table.tablename = keyname;
36                     result.tablecontent.add(keyname, table);
37                     continue;
38                 }
39                 if (result.fieldcotent.containskey(item.key)) continue;
40                 result.fieldcotent.add(item.key, item.value);
41             }
42 
43             if (request.httpmethod.equals("post", stringcomparison.currentcultureignorecase))
44             {
45                 var body = request.inputstream;
46                 var encoding = encoding.utf8;
47                 var reader = new streamreader(body, encoding);
48                 var bodycontent = reader.readtoend();
49                 var bodymodel = bodycontent.toobject<dictionary<string, object>>();
50                 foreach (var item in bodymodel)
51                 {
52                     if (item.key.startswith("image:"))
53                     {
54                         var imagemodel = item.value.tojson().toobject<imagecontentmodel>();
55                         var keyname = item.key.replace("image:", "");
56                         if (result.imagecontent.containskey(keyname))
57                             result.imagecontent[keyname] = imagemodel;
58                         else
59                             result.imagecontent.add(keyname, imagemodel);
60                         continue;
61                     }
62                     if (item.key.startswith("table:"))
63                     {
64                         var table = item.value.tojson().toobject<datatable>();
65                         var keyname = item.key.replace("table:", "");
66                         table.tablename = keyname;
67                         if (result.tablecontent.containskey(keyname))
68                             result.tablecontent[keyname] = table;
69                         else
70                             result.tablecontent.add(keyname, table);
71                         continue;
72                     }
73                     if (result.fieldcotent.containskey(item.key))
74                         result.fieldcotent[item.key] = httputility.urldecode(item.value.tostring());
75                     else
76                         result.fieldcotent.add(item.key, httputility.urldecode(item.value.tostring()));
77                 }
78             }
79             return result;
80         }

 

文档邮件合并域

  1 public class mergedocument : idisposable
  2     {
  3         public printmodel model { get; set; }
  4         public document doc { get; set; }
  5         private printfieldmergingcallback fieldcallback { get; set; }
  6         public mergedocument(printmodel model)
  7         {
  8             this.model = model;
  9             this.doc = new document(model.template);
 10             this.fieldcallback = new printfieldmergingcallback(this.model);
 11             this.doc.mailmerge.fieldmergingcallback = this.fieldcallback;
 12         }
 13         public stream mergetostream()
 14         {
 15             if (this.model.fieldcotent.count > 0)
 16                 this.doc.mailmerge.execute(this.model.fieldcotent.keys.toarray(), this.model.fieldcotent.values.toarray());
 17             if (this.model.imagecontent.count > 0)
 18             {
 19                 this.doc.mailmerge.execute(this.model.imagecontent.keys.toarray(), this.model.imagecontent.values.select(s => s.value).toarray());
 20             };
 21             if (this.model.tablecontent.count > 0)
 22             {
 23                 foreach (var item in this.model.tablecontent)
 24                 {
 25                     var table = item.value;
 26                     table.tablename = item.key;
 27                     this.doc.mailmerge.executewithregions(table);
 28                 }
 29             }
 30             this.doc.updatefields();
 31 
 32             var filename = path.combine(appdomain.currentdomain.basedirectory, "printdoc", $"{datetime.now.tostring("yymmddhhmmssfff")}.docx");
 33             var ms = new memorystream();
 34             this.doc.save(ms, saveformat.xps);
 35             return ms;
 36         }
 37 
 38         public void dispose()
 39         {
 40             this.fieldcallback.dispose();
 41         }
 42 
 43         private class printfieldmergingcallback : ifieldmergingcallback, idisposable
 44         {
 45             public httpclient client { get; set; }
 46             public printmodel model { get; set; }
 47             public printfieldmergingcallback(printmodel model)
 48             {
 49                 this.model = model;
 50                 this.client = new httpclient();
 51             }
 52             public void fieldmerging(fieldmergingargs args)
 53             {
 54             }
 55 
 56             public void imagefieldmerging(imagefieldmergingargs field)
 57             {
 58                 var fieldname = field.fieldname;
 59                 if (!this.model.imagecontent.containskey(fieldname)) return;
 60                 var imagemodel = this.model.imagecontent[fieldname];
 61                 switch (imagemodel.type)
 62                 {
 63                     case imagetype.local:
 64                         {
 65                             field.image = image.fromfile(imagemodel.value);
 66                             field.imagewidth = new mergefieldimagedimension(imagemodel.width);
 67                             field.imageheight = new mergefieldimagedimension(imagemodel.height);
 68                         };
 69                         break;
 70                     case imagetype.network:
 71                         {
 72                             var imagestream = this.client.getstreamasync(imagemodel.value).result;
 73                             var ms = new memorystream();
 74                             imagestream.copyto(ms);
 75                             ms.position = 0;
 76                             field.imagestream = ms;
 77                             field.imagewidth = new mergefieldimagedimension(imagemodel.width);
 78                             field.imageheight = new mergefieldimagedimension(imagemodel.height);
 79                         }; break;
 80                     case imagetype.barcode:
 81                         {
 82                             var barimage = this.generateimage(barcodeformat.code_128, imagemodel.value, imagemodel.width, imagemodel.height);
 83                             field.image = barimage;
 84                         }; break;
 85                     case imagetype.qrcode:
 86                         {
 87                             var qrimage = this.generateimage(barcodeformat.qr_code, imagemodel.value, imagemodel.width, imagemodel.height);
 88                             field.image = qrimage;
 89                         }; break;
 90                     default: break;
 91                 }
 92             }
 93             private bitmap generateimage(barcodeformat format, string code, int width, int height)
 94             {
 95                 var writer = new barcodewriter();
 96                 writer.format = format;
 97                 encodingoptions options = new encodingoptions()
 98                 {
 99                     width = width,
100                     height = height,
101                     margin = 2,
102                     purebarcode = false
103                 };
104                 writer.options = options;
105                 if (format == barcodeformat.qr_code)
106                 {
107                     var qroption = new qrcodeencodingoptions()
108                     {
109                         disableeci = true,
110                         characterset = "utf-8",
111                         width = width,
112                         height = height,
113                         margin = 2
114                     };
115                     writer.options = qroption;
116                 }
117                 var codeimg = writer.write(code);
118                 return codeimg;
119             }
120 
121             public void dispose()
122             {
123                 this.client.dispose();
124             }
125         }
126     }