zoukankan      html  css  js  c++  java
  • 吉特日化MES系统--通过浏览器调用标签打印

        

        三年来做制造行业,差不多做了近30个工厂,也由纯软件转入到了大量的硬件对接,包含厂房设计(这个目前还只是小菜鸟),硬件设计(只是提提意见),安装实施调试(软件和硬件撕逼操作),当然面向的对象也由计算机转向了和各种人员的对接,一直想做一个牛逼的技术人员,可是天不如人愿只能游走于各种琐事中,也很少写技术相关的文章了,今天分享一个小小的应用点《打印标签》

        一  标签打印

          在制造行业中,在仓库物流行业中标签打印是非常常用的需求,标签除了标识物体之外,我们还有一个比较重要的应用 就是在标签上附上 条码 或 二维码, 特别是二维码目前在我们生活中无处不在。  而标签中使用 条码或二维码,其实就是为了加快物体的识别效率。 当然市面上有很多打印的标签软件,各个打印机厂商也都有自带打印软件,而打印机厂商自带的软件大部分都是CS结构的程序,而且无法和BS网页交互。、

                     

           

        以上标签在生产制造行业中比较常见,除了以上在原料,生产过程中使用,还有成品仓库包装,物流发货等,当然还有一些特殊的情况比如 包装外箱等使用喷码机来喷码的情况,这种并不是使用打印来打印的。标签的应用很多,但是有一点不同的就是标签纸张规格大小的不一样,以及经常变动的表现形式。

        二  为什么不使用浏览器打印

          浏览器也是带有打印功能的,一般比较常用使用浏览器打印都是打印A4,A5 等这种比较规整的纸张,当然浏览器也能够打印各种小标签。但是浏览器打印有一个不好的就是清晰度不够,至于为什么清晰度不够这个我也说不清楚,然后在连续打印等方面都比较弱,可能很多人会问我们也经常见到连续打印的,一般这种打印都是使用浏览器插件或者和打印客户端相连接的,这也是在《吉特日化MES系统》中应用的方式。

          另外目前市面上见到的浏览器打印客户端大部分都是收费的,虽然很多费用还不低,另外就是绑架销售,比如很多打印都是集成在报表组件里面的,目前的报表组件随随便便一个授权就是几十万,这也是客户无法接受的,我们自己做项目也无法接受。

        三  自己开发的打印组件

          其实算不上自己开发的打印组件,只是基于.NET 的documentprint 打印类做了一定的封装,在之间的文字中有分享过。具体可以看看如下文章

          《吉特仓库管理系统-.NET打印问题总结

          当然也还有一些其他的打印案例的分享,有兴趣的可以翻阅一下以前的文章。

          关于自己的打印组件源代码如下:  https://github.com/hechenqingyuan/gitprint

        四  用网页打印怎么办

          上位系统的开发使用BS架构,这也就必然遇到了打印头疼的地方,如何打印标签。其实思路很简单,那就是网页出发打印指令,然后发送到客户单软件来打印,这是我这边的一个基本思路。

          

        如图是一个打印标签的操作界面,就目前而言操作还是相对比较方便了,要想达到如上效果要解决如下几个问题:

        (1)   读取本地连接的打印机(客户端获取)

    public class DBPaperSize
        {
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            internal struct structPrinterDefaults
            {
                [MarshalAs(UnmanagedType.LPTStr)]
                public String pDatatype;
                public IntPtr pDevMode;
                [MarshalAs(UnmanagedType.I4)]
                public int DesiredAccess;
            };
    
            [DllImport("winspool.Drv", EntryPoint = "OpenPrinter", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPTStr)]string printerName, out IntPtr phPrinter, ref structPrinterDefaults pd);
    
            [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern bool ClosePrinter(IntPtr phPrinter);
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            internal struct structSize
            {
                public Int32 width;
                public Int32 height;
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            internal struct structRect
            {
                public Int32 left;
                public Int32 top;
                public Int32 right;
                public Int32 bottom;
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            internal struct structFormInfo1
            {
                [MarshalAs(UnmanagedType.I4)]
                public int Flags;
                [MarshalAs(UnmanagedType.LPTStr)]
                public String pName;
                public structSize Size;
                public structRect ImageableArea;
            };
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            internal struct structDevMode
            {
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
                public String dmDeviceName;
                [MarshalAs(UnmanagedType.U2)]
                public short dmSpecVersion;
                [MarshalAs(UnmanagedType.U2)]
                public short dmDriverVersion;
                [MarshalAs(UnmanagedType.U2)]
                public short dmSize;
                [MarshalAs(UnmanagedType.U2)]
                public short dmDriverExtra;
                [MarshalAs(UnmanagedType.U4)]
                public int dmFields;
                [MarshalAs(UnmanagedType.I2)]
                public short dmOrientation;
                [MarshalAs(UnmanagedType.I2)]
                public short dmPaperSize;
                [MarshalAs(UnmanagedType.I2)]
                public short dmPaperLength;
                [MarshalAs(UnmanagedType.I2)]
                public short dmPaperWidth;
                [MarshalAs(UnmanagedType.I2)]
                public short dmScale;
                [MarshalAs(UnmanagedType.I2)]
                public short dmCopies;
                [MarshalAs(UnmanagedType.I2)]
                public short dmDefaultSource;
                [MarshalAs(UnmanagedType.I2)]
                public short dmPrintQuality;
                [MarshalAs(UnmanagedType.I2)]
                public short dmColor;
                [MarshalAs(UnmanagedType.I2)]
                public short dmDuplex;
                [MarshalAs(UnmanagedType.I2)]
                public short dmYResolution;
                [MarshalAs(UnmanagedType.I2)]
                public short dmTTOption;
                [MarshalAs(UnmanagedType.I2)]
                public short dmCollate;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
                public String dmFormName;
                [MarshalAs(UnmanagedType.U2)]
                public short dmLogPixels;
                [MarshalAs(UnmanagedType.U4)]
                public int dmBitsPerPel;
                [MarshalAs(UnmanagedType.U4)]
                public int dmPelsWidth;
                [MarshalAs(UnmanagedType.U4)]
                public int dmPelsHeight;
                [MarshalAs(UnmanagedType.U4)]
                public int dmNup;
                [MarshalAs(UnmanagedType.U4)]
                public int dmDisplayFrequency;
                [MarshalAs(UnmanagedType.U4)]
                public int dmICMMethod;
                [MarshalAs(UnmanagedType.U4)]
                public int dmICMIntent;
                [MarshalAs(UnmanagedType.U4)]
                public int dmMediaType;
                [MarshalAs(UnmanagedType.U4)]
                public int dmDitherType;
                [MarshalAs(UnmanagedType.U4)]
                public int dmReserved1;
                [MarshalAs(UnmanagedType.U4)]
                public int dmReserved2;
            }
    
            [DllImport("winspool.Drv", EntryPoint = "AddForm", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern bool AddForm(IntPtr phPrinter, [MarshalAs(UnmanagedType.I4)]   int level, ref structFormInfo1 form);
            [DllImport("winspool.Drv", EntryPoint = "DeleteForm", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern bool DeleteForm(IntPtr phPrinter, [MarshalAs(UnmanagedType.LPTStr)]   string pName);
            [DllImport("winspool.Drv", EntryPoint = "SetForm", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern bool SetForm(IntPtr phPrinter, [MarshalAs(UnmanagedType.LPTStr)]   string pName, [MarshalAs(UnmanagedType.I4)]   int level, ref structFormInfo1 form);
            [DllImport("kernel32.dll", EntryPoint = "GetLastError", SetLastError = false, ExactSpelling = true, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern Int32 GetLastError();
            [DllImport("GDI32.dll", EntryPoint = "CreateDC", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern IntPtr CreateDC([MarshalAs(UnmanagedType.LPTStr)]string pDrive, [MarshalAs(UnmanagedType.LPTStr)]   string pName, [MarshalAs(UnmanagedType.LPTStr)]   string pOutput, ref structDevMode pDevMode);
            [DllImport("GDI32.dll", EntryPoint = "ResetDC", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern IntPtr ResetDC(IntPtr hDC, ref structDevMode pDevMode);
            [DllImport("GDI32.dll", EntryPoint = "DeleteDC", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurityAttribute()]
            internal static extern bool DeleteDC(IntPtr hDC);
    
            public System.Drawing.Printing.PaperSize GetPrintForm(string printerName, string paperName)
            {
                System.Drawing.Printing.PaperSize paper = null;
                System.Drawing.Printing.PrinterSettings printer = new System.Drawing.Printing.PrinterSettings();
                printer.PrinterName = printerName;
                foreach (System.Drawing.Printing.PaperSize ps in printer.PaperSizes)
                {
                    if (ps.PaperName.ToLower() == paperName.ToLower())
                    {
                        paper = ps;
                        break;
                    }
                }
                return paper;
            }
    
            public void SetPrintForm(string printerName, string paperName, float width, float height)
            {
                if (PlatformID.Win32NT == Environment.OSVersion.Platform)
                {
                    const int PRINTER_ACCESS_USE = 0x00000008;
                    const int PRINTER_ACCESS_ADMINISTER = 0x00000004;
                    structPrinterDefaults defaults = new structPrinterDefaults();
                    defaults.pDatatype = null;
                    defaults.pDevMode = IntPtr.Zero;
                    defaults.DesiredAccess = PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE;
                    IntPtr hPrinter = IntPtr.Zero;
                    if (OpenPrinter(printerName, out hPrinter, ref defaults))
                    {
                        try
                        {
                            structFormInfo1 formInfo = new structFormInfo1();
                            formInfo.Flags = 0;
                            formInfo.pName = paperName;
                            formInfo.Size.width = (int)(width * 100.0);
                            formInfo.Size.height = (int)(height * 100.0);
                            formInfo.ImageableArea.left = 0;
                            formInfo.ImageableArea.right = formInfo.Size.width;
                            formInfo.ImageableArea.top = 0;
                            formInfo.ImageableArea.bottom = formInfo.Size.height;
                            bool rslt = false;
                            if (GetPrintForm(printerName, paperName) != null)
                            {
                                rslt = SetForm(hPrinter, paperName, 1, ref formInfo);
                            }
                            else
                            {
                                this.AddCustomPaperSize(printerName, paperName, width, height);
                                rslt = true;
                            }
                            if (!rslt)
                            {
                                StringBuilder strBuilder = new StringBuilder();
                                strBuilder.AppendFormat("添加纸张{0}时发生错误!,   系统错误号:   {1}", paperName, GetLastError());
                                MessageBox.Show(strBuilder.ToString());
                            }
                        }
                        finally
                        {
                            ClosePrinter(hPrinter);
                        }
                    }
                }
            }
    
            public void AddCustomPaperSize(string printerName, string paperName, float width, float height)
            {
                if (PlatformID.Win32NT == Environment.OSVersion.Platform)
                {
                    const int PRINTER_ACCESS_USE = 0x00000008;
                    const int PRINTER_ACCESS_ADMINISTER = 0x00000004;
                    structPrinterDefaults defaults = new structPrinterDefaults();
                    defaults.pDatatype = null;
                    defaults.pDevMode = IntPtr.Zero;
                    defaults.DesiredAccess = PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE;
                    IntPtr hPrinter = IntPtr.Zero;
                    if (OpenPrinter(printerName, out hPrinter, ref defaults))
                    {
                        try
                        {
                            DeleteForm(hPrinter, paperName);
                            structFormInfo1 formInfo = new structFormInfo1();
                            formInfo.Flags = 0;
                            formInfo.pName = paperName;
                            formInfo.Size.width = (int)(width * 100.0);
                            formInfo.Size.height = (int)(height * 100.0);
                            formInfo.ImageableArea.left = 0;
                            formInfo.ImageableArea.right = formInfo.Size.width;
                            formInfo.ImageableArea.top = 0;
                            formInfo.ImageableArea.bottom = formInfo.Size.height;
                            if (!AddForm(hPrinter, 1, ref formInfo))
                            {
                                StringBuilder strBuilder = new StringBuilder();
                                strBuilder.AppendFormat("添加纸张{0}时发生错误!,   系统错误号:   {1}", paperName, GetLastError());
                                throw new ApplicationException(strBuilder.ToString());
                            }
                        }
                        finally
                        {
                            ClosePrinter(hPrinter);
                        }
                    }
                    else
                    {
                        StringBuilder strBuilder = new StringBuilder();
                        strBuilder.AppendFormat("打开打印机{0}   时出现异常!,   系统错误号:   {1}", printerName, GetLastError());
                        throw new ApplicationException(strBuilder.ToString());
                    }
                }
                else
                {
                    structDevMode pDevMode = new structDevMode();
                    IntPtr hDC = CreateDC(null, printerName, null, ref pDevMode);
                    if (hDC != IntPtr.Zero)
                    {
                        const long DM_PAPERSIZE = 0x00000002L;
                        const long DM_PAPERLENGTH = 0x00000004L;
                        const long DM_PAPERWIDTH = 0x00000008L;
                        pDevMode.dmFields = (int)(DM_PAPERSIZE | DM_PAPERWIDTH | DM_PAPERLENGTH);
                        pDevMode.dmPaperSize = 256;
                        pDevMode.dmPaperWidth = (short)(width * 2.54 * 10000.0);
                        pDevMode.dmPaperLength = (short)(height * 2.54 * 10000.0);
                        ResetDC(hDC, ref pDevMode);
                        DeleteDC(hDC);
                    }
                }
            }
    
            [DllImport("Winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern bool SetDefaultPrinter(string printerName);
    
            [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern bool GetDefaultPrinter(StringBuilder pszBuffer, ref int pcchBuffer);
    
            public static bool SetDefaultPrint(string printName)
            {
                return SetDefaultPrinter(printName);
            }
    
            /// <summary>
            /// 获取所有打印机名称
            /// </summary>
            /// <returns></returns>
            public List<string> GetLocalPrinters()
            {
                List<string> fPrinters = new List<string>();
                string defaultprint = DBPaperSize.GetDefaultPrinter();
                if (!string.IsNullOrEmpty(defaultprint))
                {
                    fPrinters.Add(defaultprint);
                }
                foreach (string fPrinterName in PrinterSettings.InstalledPrinters)
                {
                    if (!fPrinters.Contains(fPrinterName))
                        fPrinters.Add(fPrinterName);
                }
                //fPrinters = fPrinters.Where(item => item.ToLower().Contains("zdesigner")).ToList();
                return fPrinters;
            }
    
            /// <summary>
            /// 获取默认的打印机
            /// </summary>
            /// <returns></returns>
            public static string GetDefaultPrinter()
            {
                const int ERROR_FILE_NOT_FOUND = 2;
    
                const int ERROR_INSUFFICIENT_BUFFER = 122;
    
                int pcchBuffer = 0;
    
                if (GetDefaultPrinter(null, ref pcchBuffer))
                {
                    return null;
                }
    
                int lastWin32Error = Marshal.GetLastWin32Error();
    
                if (lastWin32Error == ERROR_INSUFFICIENT_BUFFER)
                {
                    StringBuilder pszBuffer = new StringBuilder(pcchBuffer);
                    if (GetDefaultPrinter(pszBuffer, ref pcchBuffer))
                    {
                        return pszBuffer.ToString();
                    }
    
                    lastWin32Error = Marshal.GetLastWin32Error();
                }
                if (lastWin32Error == ERROR_FILE_NOT_FOUND)
                {
                    return null;
                }
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    View Code

        (2)   网页获取本地连接打印机

          其实这个比较简单,就是将客户端启动时候,获取本地连接的打印机,然后发送到服务端,然后服务端打开选择打印机时获取本地注册的打印机。注册的打印机信息使用Redis缓存,为什么使用Redis 是有用处的.

        (3)将网页发送的执行传递给打印客户端

            其实我们知道Redis除了缓存功能之外,还有简单的订阅发布的功能,将消息传递给打印客户端我们暂时就是使用的Redis,因为属于小量的消息发布订阅个人觉得Redis还比较合适,但是问题也会有,也有很多其他的方式解决。

          (4)   打印机的上线和下线

            这个其实就是取决于打印客户端是否启动了,其实是否真正连接到打印其实还是打印客户端说了算。当打印客户端启动的时候,将本地打印机信息发送到服务端,关闭的时候发送请求注销服务端缓存的打印机信息。所以为了能够完整的实现打印,打印客户端必须启动,这也是我们在实施过程中很难搞的部分,因为要打开浏览器又要打开客户端(目前我们的办法是设置开机自动启动打印客户端),客户觉得很麻烦。

            另外Redis 掉线的问题,必须考虑重连,否则就无法接受到推送的信息,另外重复连接又导致消息订阅重复,导致一个指令可以打印多张标签出来。

        五  将打印客户端注册成本地服务

          上面说的这种方式是我们用的比较多的一种方式打印,但是有点就是如果有多个打印机站点,我选择的时候可以看到全工厂所有连接的打印机,这个选择就很头疼了。于是就出现了本机网页只调用本地的打印机服务。

          这个时候我们就需要将本地的打印服务发布成一个可调用的http服务,IP地址指向127.0.0.1  ,由JS调用本地服务打印,至于如何转化为http服务,可以参考文章

          <<吉特日化MES&WMS系统--三色灯控制协议转http>>

        六   WebSocket  解决通讯问题

          前面也提到了,解决问题的思路和方式都比较简单,也比较固话,只要解决操作端和打印机之间的通讯问题即可,我们还可以使用到WebSocket方式连接网页与打印客户端程序。

        七   Windows 强大的注册表

          你知道从淘宝网页上如何打开一个 阿里旺旺 聊天工具么,怎么在网页上唤醒 QQ 客服,这两个给了我一个比较不错的思路,我通过网页唤醒一个打印客户端程序,及时客户端未打开的情况下。

          《吉特仓库管理系统(开源)-如何在网页端启动WinForm 程序

          只要能够启动打印客户端,就能够传递参数发送打印指令。

        八  移动终端调用打印

          说到这里绝大部分想到的可以使用蓝牙打印机,没错,蓝牙打印机的确是很不错的一个选择,但是有一点就是打印指令的编排问题,以及难以设置的打印格式。再就是我们开发移动终端的时候使用的是H5, 调用蓝牙通讯这个稳定性实在不敢恭维(当然也有可能是我们的技术能力太弱了,不求甚解)。

          其实我们的方式还是采用  4 章节提到的思路,H5 ,http 协议这个不是很完美的方式么,H5 终端发送http指令,通过Redis推送打印消息到打印客户端

      

        选择好打印机之后,然后确定就可以顺利打印出标签了。

         以上的方式和方法在之前分享文字以及共享的代码中都有提到使用,只是这一次是集中汇总一次,虽然是小功能但是感觉是应用软件的刚需问题,说简单也简单说复杂也还有那么一些小的弯弯绕绕。

         在这几年的工作中,虽然很多精力被分散到其他的事情上去了,但是也从其他领域学到了很多东西,希望有更多的时间能够记录这些经历和经验,这几年的工作领域跨度是非常大的,虽然年纪上来了也有些迷茫和担忧,但是一腔热血暂时还没有消退。世界之大,制造业领域的应用也跨越了众多学科,要学的东西还很多,虽然和高喊着  智能制造,数字化转型,工业互联网,5G+,大数据工业应用的专家和大佬比起来有很大的差距,自己更希望能够从工业制造的工艺应用上去有点突破,虽然要顺势而为但也要实事求是。

        这几年主要工作都是从事  日化产品,化妆品类的工厂生产制造,包括  家用清洗剂,牙膏,香水,粉底彩妆,护肤,香水,保健品以及制药方面,希望有更多自己的时间来反思记录这些年遇到的问题。

  • 相关阅读:
    JQuery
    C#基础知识
    CSS
    学习.NET
    Grunt常见问题
    一个小型的类库
    SQL字符串处理!
    安装Java8以后,Eclipse运行异常解决方案
    谈薪四式让你谈好薪
    使用js实现input输入框的增加
  • 原文地址:https://www.cnblogs.com/qingyuan/p/14706223.html
Copyright © 2011-2022 走看看