代码改变世界

吉特日化MES系统--通过浏览器调用标签打印

2021-04-27 10:45  贺臣  阅读(1668)  评论(0编辑  收藏  举报

    

    三年来做制造行业,差不多做了近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+,大数据工业应用的专家和大佬比起来有很大的差距,自己更希望能够从工业制造的工艺应用上去有点突破,虽然要顺势而为但也要实事求是。

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

 


作者:情缘
出处:http://www.cnblogs.com/qingyuan/
关于作者:从事仓库,生产软件方面的开发,在项目管理以及企业经营方面寻求发展之路
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
联系方式: 个人QQ  821865130 ; 仓储技术QQ群 88718955,142050808 ;
吉特仓储管理系统 开源地址: https://github.com/hechenqingyuan/gitwms