2021-06-25

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

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

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

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

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

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

 

打印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版本就成了。

 

 


下图为位置的配置文件

 

 

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

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


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

 

 

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

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

 

打印2.0

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

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

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

只到问了很多相关的相关人士。最后给了我一个第三方的商业解决方案BarTender
相关参考:https://www.bartendersoftware.com/
这个有自己的模板编辑器,

 

 

 

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

根据他的SDK,同时安装了相关程序,写下几句打印代码。一个基于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    //   try110    //   {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, // HANDLE173    IntPtr completionEvent, // HANDLE174    [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 IXpsPrintJobStream204  {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 IXpsPrintJob215  {216   void Cancel();217   void GetJobStatus(out XPS_JOB_STATUS jobStatus);218  }219 220  [StructLayout(LayoutKind.Sequential)]221  struct XPS_JOB_STATUS222  {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; // UInt32229  };230 231  enum XPS_JOB_COMPLETION232  {233   XPS_JOB_IN_PROGRESS = 0,234   XPS_JOB_COMPLETED = 1,235   XPS_JOB_CANCELLED = 2,236   XPS_JOB_FAILED = 3237  }238 239  enum WAIT_RESULT240  {241   WAIT_OBJECT_0 = 0,242   WAIT_ABANDONED = 0x80,243   WAIT_TIMEOUT = 0x102,244   WAIT_FAILED = -1 // 0xFFFFFFFF245  }246 }

XpsPrintHelper

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

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

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

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

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

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

下图为Word模板编辑

 

 编辑占位符(域)

 

 

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

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

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

 

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

对于上面的模板,只要发送这样的请球POST

对于Get请求

 

然后打印出来的效果

到此,打印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       else59        result.ImageContent.Add(keyName, imageModel);60       continue;61      }62      if (item.Key.StartsWith("Table:"))63      {64       var table = item.Value.ToJson().ToO......

原文转载:http://www.shaoqun.com/a/828792.html

跨境电商:https://www.ikjzd.com/

香港会计师事务所:https://www.ikjzd.com/w/2434

跨境通:https://www.ikjzd.com/w/1329

慧聪集团:https://www.ikjzd.com/w/1836


最近做了几个项目,都有在产品贴标的需求基本就是有个证卡类打印机,然后把产品的信息打印在标签上。然后通过机器人把标签贴到产品上面标签信息包括文本,二维码,条形码之类的,要根据对应的数据生成二维码,条形码。打印标签的需求接到手后,开始了我的填坑之旅。打印3.0源代码:https://github.com/zeqp/ZEQP.Print打印1.0第一个项目开始,因为原来没有研究过打印,所以在Bing上查
北纬30°纬线上四川风光独好 携手央视四川旅游营销再创新 :http://www.30bags.com/a/414067.html
易联通:https://www.ikjzd.com/w/1854.html
婆婆真是极品 什么钱她都要拿:http://lady.shaoqun.com/a/79273.html
公么吃奶摸下面非常好 强壮的公么征服我厨房:http://lady.shaoqun.com/a/247964.html
广西秋季登高好去处,你都知道哪些?:http://www.30bags.com/a/429865.html
国际物流杂费解密,外贸人别贪图一时便宜!:https://www.ikjzd.com/articles/6021
亚马逊近况:美国站后台异常,日本站遭遇台风和地震!:https://www.ikjzd.com/articles/6023
台风未走、地震又来,日本站卖家遭遇订单暴跌和断货危机!:https://www.ikjzd.com/articles/6029
两个黑人挺进白领校花体内 被黑人塞得满满的好粗:http://lady.shaoqun.com/m/a/247537.html
一个释放这样信号的女人,明明是暗恋你,不懂:http://lady.shaoqun.com/a/387401.html
深圳光明区党史馆开馆了吗?:http://www.30bags.com/a/456070.html
深圳党史馆方志馆临时闭馆须知:http://www.30bags.com/a/456071.html

No comments:

Post a Comment