Several projects have been done recently, and there is a demand for product labeling

Basically, there is a card printer, and then the product information is printed on the label.

Then use a robot to attach the label to the product

The label information includes text, two-dimensional code, bar code and the like, and two-dimensional code and bar code should be generated according to corresponding data.

After receiving the demand for printing labels, I started my journey to fill the pits.

 

Print 3.0 source code:
https://github.com/zeqp/ZEQP.Print

 

Print 1.0

The first project started, because I didn’t study printing, so I checked the information of .Net printer on Bing.

Found that it is basically based on .net’s System.Drawing.Printing.PrintDocument class to do custom printing

Everyone uses this for printing. I think there is no problem.

So started my code.

PrintDocument to print is nothing more than setting the printer name, DefaultPageSettings.PrinterSettings.PrinterName, the number of copies, DefaultPageSettings.PrinterSettings.Copies, paper orientation, DefaultPageSettings.Landscape, and then printing the specific information is to write the event PrintPage and then call Graphics.DrawString, Graphics.DrawImage To write specific text and pictures Graphics.Draw, you need to specify font, color, position and other data. I made these configuration data. Then version 1.0 became.Label_printing_based_on_Win_service_(template_printing)_using_dotnet_0.png

 

 

The following figure is the configuration file of the locationLabel_printing_based_on_Win_service_(template_printing)_using_dotnet_1.png

 

 

Once the code is written, when debugging with VS. Run to fly. ,

The position of all fonts and data to be printed can also be adjusted dynamically through configuration files. It feels perfect. But the reality is very skinny, I patted my face right away

The PrintDocument class can only be run as a WinForm, not as a service. For details, please refer to:
https://docs.microsoft.com/zh-cn/dotnet/api/system.drawing.printing?redirectedfrom=MSDN&view=netframework-4.8

Label_printing_based_on_Win_service_(template_printing)_using_dotnet_2.png

 

 

Fortunately, there are no requirements from customers, and there will be a special host computer to do this during production, so a WinForm without interface is made. Run when the computer starts

This solves the problem of not being able to run as a service.

 

Print 2.0

After printing 1.0, I received another project. There are printing related functions again, naturally assigned to me again.

But for the previous version of printing. Can not run as a service, as a program written by myself, there are such big flaws. Always feeling upset

I want to solve this problem, but all prints that found .Net on Bing do this. There is no better way.

Only to ask a lot of relevant stakeholders. Finally gave me a third-party commercial solution related to BarTender : https://www.bartendersoftware.com/ this has its own template editor,Label_printing_based_on_Win_service_(template_printing)_using_dotnet_3.png

 

 

 

It has its own SDK and editor, and its functions are not powerful. It is indeed a commercial printing solution.

According to his SDK, related programs are installed at the same time, and a few lines of print code are written. A print based on Win service

Label_printing_based_on_Win_service_(template_printing)_using_dotnet_4.png

 

 

then. Print 2.0 came out.

 

Printing 3.0 But for a third-party-based commercial printing solution, all functions are very powerful. The implementation is also simple.

For small projects for general companies. Earned money is not enough to buy a license for this commercial suite

And for a program that will only use someone else ’s SDK. Not a soulful program.

Because you do n’t know how this is done behind the scenes. The principle is nothing.

For me, I can finish this project with BarTender. But I’m not always satisfied with this printing scheme.

Because I just added a shell on top of this. I don’t know what was done later.

So I always wanted to develop a printing program that can be run based on Win services. It’s best to have your own template editor.

Only one day. Accidentally found an article

https://docs.aspose.com/display/wordsnet/Print+a+Document

He also explained here that problems related to service-based printing cannot be solved.

And they have found the corresponding solution. Based on his solution. I wrote a print helper class.

This is based on Windows XPS document API printing.

XPS is an analogy of PDF document type supported by Windows after Windows 7

Explanation of basic 
XpsPrint API

At the same time basically his XPS printing help class. I did the test. Can run off printing perfectly in Windows Services.

  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 }

XpsPrintHelper

At this point, printing based on windows services has been resolved.

It’s just the template editor.

For previous experience with Word-based mail merge domains. A lot of work for developing an editor yourself

So I chose an existing, powerful document editor. Word is my label editor.

Word can perfectly solve the problems of paper, format, position and so on. Just use “text field” as a placeholder in the corresponding place

Then populate it with custom data.

The following figure is a word template editorLabel_printing_based_on_Win_service_(template_printing)_using_dotnet_5.png

 

Edit placeholder (domain)Label_printing_based_on_Win_service_(template_printing)_using_dotnet_6.png

 

 

In this case. A template comes out

If it is a picture. Add Image just before the domain name:

If it is a form. Add TableStart at the beginning of the table: Table name Add TableEnd at the end of the table: Table name

 

Agreement words. Going http is supported by all languages, which is also convenient for developing SDK in the future

For the above template, just send this request ball postLabel_printing_based_on_Win_service_(template_printing)_using_dotnet_7.png

For Get requests

Label_printing_based_on_Win_service_(template_printing)_using_dotnet_8.png

 

And then print the effect

Label_printing_based_on_Win_service_(template_printing)_using_dotnet_9.png

At this point, printing 3.0 has been completed.

Key code generates print entity based on request data

 

 

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                  vartempPath = 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          }

 

 

GetPrintModel

 

Document mail merge domain

  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     }

MergeDocument

 

 

 

 

 

 

 

 

Orignal link:https://www.cnblogs.com/liuju150/p/Service_Print_Template_Solution.html