Windows 下 C# 打印机操作方法

2022年12月3日 2800点热度 4人点赞 0条评论
内容纲要

背景:

查找了很多库,要么收费,要么太旧用不了。

经过大量测试,写了打印机的相关代码。

实现的代码不依赖于第三方库。

核心代码

引入这两个库:

System.Drawing.Printing
Vanara.PInvoke.Printing

这两个库用于使用 winspool.drv 服务,可以避免编写大量 库函数调用代码。

首先编写基础代码:

    public class PrinterBase
    {
        /// <summary>
        /// 获取默认打印机
        /// </summary>
        /// <returns></returns>
        public static string GetDefaultPrinter()
        {
            // PrintDocument 对象中默认会使用默认打印机
            PrintDocument print = new PrintDocument();
            return print.PrinterSettings.PrinterName;
        }

        /// <summary>
        /// 获取本地所有打印机
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<string> GetLocalPrinters()
        {
            var enumerator = PrinterSettings.InstalledPrinters.GetEnumerator();
            while (enumerator.MoveNext())
            {
                var name = enumerator.Current as string;
                yield return name;
            }
        }
    }

简单输出打印机的属性:

    static void Main()
    {
        var ps = PrinterBase.GetLocalPrinters();
        foreach (var name in ps)
        {
            PrintDocument print = new PrintDocument();
            print.PrinterSettings.PrinterName = name;   // 设置打印机名字后,PrintDocument 会自动切换为对应打印机的实例

            Console.WriteLine($"打印机名称:{name}");
            Console.WriteLine($"可用的打印机:{print.PrinterSettings.IsValid}");
            Console.WriteLine($"默认的打印机:{print.PrinterSettings.IsDefaultPrinter}");
            Console.WriteLine($"是否是绘图仪的值:{print.PrinterSettings.IsPlotter}");
            Console.WriteLine($"支持彩色打印:{print.PrinterSettings.SupportsColor}");
            Console.WriteLine($"支持双面打印:{print.PrinterSettings.CanDuplex}");
            Console.WriteLine($"-------------------------------------------");
        }

    }
打印机名称:导出为WPS PDF
可用的打印机:True
默认的打印机:False
是否是绘图仪的值:False
支持彩色打印:True
支持双面打印:False
-------------------------------------------
打印机名称:OneNote
可用的打印机:True
默认的打印机:False
是否是绘图仪的值:False
支持彩色打印:True
支持双面打印:False
-------------------------------------------
打印机名称:Microsoft XPS Document Writer
可用的打印机:True
默认的打印机:False
是否是绘图仪的值:False
支持彩色打印:True
支持双面打印:False
-------------------------------------------
打印机名称:Microsoft Print to PDF
可用的打印机:True
默认的打印机:False
是否是绘图仪的值:False
支持彩色打印:True
支持双面打印:False
-------------------------------------------
打印机名称:Fax
可用的打印机:True
默认的打印机:False
是否是绘图仪的值:False
支持彩色打印:False
支持双面打印:False
-------------------------------------------
打印机名称:Deli DL-888D
可用的打印机:True
默认的打印机:True
是否是绘图仪的值:False
支持彩色打印:False
支持双面打印:False
-------------------------------------------

信息的属性可以参考:PrinterSettings
file

https://learn.microsoft.com/zh-cn/dotnet/api/system.drawing.printing.printersettings?view=dotnet-plat-ext-7.0

如果要获取像下图中的信息,会有些麻烦。

file

获取打印机状态

https://learn.microsoft.com/zh-cn/windows/win32/printdocs/printer-info-2
打印机状态。 此成员可以是以下值的任何合理组合。

使用封装的框架,但是不一定可以实时获取到,发现 wps 也有这个问题。

就是把打印机拔下来了,程序无法检测到打印机的实际情况。

而且还有个问题,打印机状态值可以是枚举的各种组合,这个问题

file

枚举值列表:

含义
PRINTER_STATUS_BUSY 打印机正忙。
PRINTER_STATUS_DOOR_OPEN 打印机门已打开。
PRINTER_STATUS_ERROR 打印机处于错误状态。
PRINTER_STATUS_INITIALIZING 打印机正在初始化。
PRINTER_STATUS_IO_ACTIVE 打印机处于活动输入/输出状态
PRINTER_STATUS_MANUAL_FEED 打印机处于手动馈送状态。
PRINTER_STATUS_NO_TONER 打印机墨粉用完。
PRINTER_STATUS_NOT_AVAILABLE 打印机不可用于打印。
PRINTER_STATUS_OFFLINE 打印机处于脱机状态。
PRINTER_STATUS_OUT_OF_MEMORY 打印机内存不足。
PRINTER_STATUS_OUTPUT_BIN_FULL 打印机的输出纸盒已满。
PRINTER_STATUS_PAGE_PUNT 打印机无法打印当前页。
PRINTER_STATUS_PAPER_JAM 纸张在打印机中被堵塞
PRINTER_STATUS_PAPER_OUT 打印机缺纸。
PRINTER_STATUS_PAPER_PROBLEM 打印机有纸张问题。
PRINTER_STATUS_PAUSED 打印机已暂停。
PRINTER_STATUS_PENDING_DELETION 正在删除打印机。
PRINTER_STATUS_POWER_SAVE 打印机处于节能模式。
PRINTER_STATUS_PRINTING 打印机正在打印。
PRINTER_STATUS_PROCESSING 打印机正在处理打印作业。
PRINTER_STATUS_SERVER_UNKNOWN 打印机状态未知。
PRINTER_STATUS_TONER_LOW 打印机在墨盒上处于低位。
PRINTER_STATUS_USER_INTERVENTION 打印机有一个错误,要求用户执行某些操作。
PRINTER_STATUS_WAITING 打印机正在等待。
PRINTER_STATUS_WARMING_UP 打印机正在预热。
        #region 打印机状态

        // 获取打印机状态代码
        private static WinSpool.PRINTER_STATUS GetPrinterStatusCodeInt(string printerName)
        {
            WinSpool.PRINTER_STATUS intRet = default;
            WinSpool.SafeHPRINTER hPrinter;

            if (WinSpool.OpenPrinter(printerName, out hPrinter))
            {
                uint cbNeeded = 0;
                bool bolRet = WinSpool.GetPrinter(hPrinter, 2, IntPtr.Zero, 0, out cbNeeded);
                if (cbNeeded > 0)
                {
                    IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
                    bolRet = WinSpool.GetPrinter(hPrinter, 2, pAddr, cbNeeded, out cbNeeded);
                    if (bolRet)
                    {
                        WinSpool.PRINTER_INFO_2 Info2 = new WinSpool.PRINTER_INFO_2();
                        Info2 = (WinSpool.PRINTER_INFO_2)Marshal.PtrToStructure(pAddr, typeof(WinSpool.PRINTER_INFO_2));
                        intRet = Info2.Status;
                    }
                    Marshal.FreeHGlobal(pAddr);
                }
                WinSpool.ClosePrinter(hPrinter);
            }
            return intRet;
        }

        // 将打印机状态代码转换为对应信息
        private static string PrinterStatusToMessage(WinSpool.PRINTER_STATUS intStatusCodeValue)
        {
            if (intStatusCodeValue == 0)
            {
                return "打印机已经准备就绪";
            }
            switch (intStatusCodeValue)
            {
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_BUSY) == PRINTER_STATUS.PRINTER_STATUS_BUSY:
                    return "打印机正忙;";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_DOOR_OPEN) == PRINTER_STATUS.PRINTER_STATUS_DOOR_OPEN:
                    return "打印机门已打开";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_ERROR) == PRINTER_STATUS.PRINTER_STATUS_ERROR:
                    return "打印机处于错误状态";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_INITIALIZING) == PRINTER_STATUS.PRINTER_STATUS_INITIALIZING:
                    return "打印机正在初始化";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_IO_ACTIVE) == PRINTER_STATUS.PRINTER_STATUS_IO_ACTIVE:
                    return "打印机处于活动输入/输出状态";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_MANUAL_FEED) == PRINTER_STATUS.PRINTER_STATUS_MANUAL_FEED:
                    return "打印机处于手动馈送状态";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_NOT_AVAILABLE) == PRINTER_STATUS.PRINTER_STATUS_NOT_AVAILABLE:
                    return "打印机不可用于打印";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_NO_TONER) == PRINTER_STATUS.PRINTER_STATUS_NO_TONER:
                    return "打印机墨粉用完";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_OFFLINE) == PRINTER_STATUS.PRINTER_STATUS_OFFLINE:
                    return "打印机处于脱机状态";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_OUTPUT_BIN_FULL) == PRINTER_STATUS.PRINTER_STATUS_OUTPUT_BIN_FULL:
                    return "打印机的输出纸盒已满";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_OUT_OF_MEMORY) == PRINTER_STATUS.PRINTER_STATUS_OUT_OF_MEMORY:
                    return "打印机内存不足";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAGE_PUNT) == PRINTER_STATUS.PRINTER_STATUS_PAGE_PUNT:
                    return "打印机无法打印当前页";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAPER_JAM) == PRINTER_STATUS.PRINTER_STATUS_PAPER_JAM:
                    return "纸张在打印机中被堵塞";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAPER_OUT) == PRINTER_STATUS.PRINTER_STATUS_PAPER_OUT:
                    return "打印机缺纸";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAPER_PROBLEM) == PRINTER_STATUS.PRINTER_STATUS_PAPER_PROBLEM:
                    return "打印机有纸张问题";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAUSED) == PRINTER_STATUS.PRINTER_STATUS_PAUSED:
                    return "打印机已暂停";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PENDING_DELETION) == PRINTER_STATUS.PRINTER_STATUS_PENDING_DELETION:
                    return "正在删除打印机";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_POWER_SAVE) == PRINTER_STATUS.PRINTER_STATUS_POWER_SAVE:
                    return "打印机处于节能模式";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PRINTING) == PRINTER_STATUS.PRINTER_STATUS_PRINTING:
                    return "打印机正在打印";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PROCESSING) == PRINTER_STATUS.PRINTER_STATUS_PROCESSING:
                    return "打印机正在处理打印作业";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_SERVER_OFFLINE) == PRINTER_STATUS.PRINTER_STATUS_SERVER_OFFLINE:
                    return "打印机服务离线";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_SERVER_UNKNOWN) == PRINTER_STATUS.PRINTER_STATUS_SERVER_UNKNOWN:
                    return "打印机状态未知";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_TONER_LOW) == PRINTER_STATUS.PRINTER_STATUS_TONER_LOW:
                    return "打印机在墨盒上处于低位";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_USER_INTERVENTION) == PRINTER_STATUS.PRINTER_STATUS_USER_INTERVENTION:
                    return "打印机有一个错误,要求用户执行某些操作";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_WAITING) == PRINTER_STATUS.PRINTER_STATUS_WAITING:
                    return "打印机正在等待";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_WARMING_UP) == PRINTER_STATUS.PRINTER_STATUS_WARMING_UP:
                    return "打印机正在预热";
                case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_DRIVER_UPDATE_NEEDED) == PRINTER_STATUS.PRINTER_STATUS_DRIVER_UPDATE_NEEDED:
                    return "打印机需要更新驱动";
            }
            return "未知";
        #endregion

因为状态码是可以组合的,这里只返回了其中一个错误,如果改成 if,会好一些。

        /// <summary>
        /// 获取指定的打印机状态信息
        /// </summary>
        /// <remarks>需要打开打印机然后关闭打印机,需要消耗一些时间,会慢一些</remarks>
        /// <param name="printerName"></param>
        /// <returns></returns>
        public static string GetPrinterStatusMessage(string printerName)
        {
            var code = GetPrinterStatusCodeInt(printerName);
            var message = PrinterStatusToMessage(code);
            return message;

弹出打印机属性

打击 “属性时”,弹出当前打印机的属性窗口。

file

示例代码如下:

        /// <summary>
        /// 弹出打印机属性窗口
        /// </summary>
        /// <param name="printerName"></param>
        public static void OpenPrinterPropertiesDialog(string printerName)
        {
            if (printerName != null && printerName.Length > 0)
            {
                SafeHPRINTER pPrinter;
                IntPtr pDevModeOutput = IntPtr.Zero;
                IntPtr pDevModeInput = IntPtr.Zero;

                OpenPrinter(printerName, out pPrinter);

                int iNeeded = DocumentProperties(IntPtr.Zero, pPrinter, printerName, pDevModeOutput, pDevModeInput, 0);
                pDevModeOutput = System.Runtime.InteropServices.Marshal.AllocHGlobal(iNeeded);
                DocumentProperties(IntPtr.Zero, pPrinter, printerName, pDevModeOutput, pDevModeInput, DM.DM_PROMPT);
                ClosePrinter(pPrinter);
            }
        }

打印文件

打印文件需要使用 PrintDocument,能够打印什么样的文件,在于使用的 PrintPage 事件,打印的核心代码如下:

        public static void PrintfFile(string path, PrinterSettings printerSettings)
        {
            var streamToPrint = new StreamReader(path);
            var printFont = new Font("Arial", 10);
            try
            {
                PrintDocument pd = new PrintDocument();
                pd.PrintPage += (o, e) =>
                {
                    pd_PrintPage(o, e);
                };
                pd.Print();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            finally
            {
                streamToPrint.Close();
            }
        }

如果要打印文本:

            void pd_PrintPage(object sender, PrintPageEventArgs ev)
            {
                float linesPerPage = 0;
                float yPos = 0;
                int count = 0;
                float leftMargin = ev.MarginBounds.Left;
                float topMargin = ev.MarginBounds.Top;
                string line = null;

                // Calculate the number of lines per page.
                linesPerPage = ev.MarginBounds.Height /
                   printFont.GetHeight(ev.Graphics);

                // Print each line of the file.
                while (count < linesPerPage &&
                   ((line = streamToPrint.ReadLine()) != null))
                {
                    yPos = topMargin + (count *
                       printFont.GetHeight(ev.Graphics));
                    ev.Graphics.DrawString(line, printFont, Brushes.Black,
                       leftMargin, yPos, new StringFormat());
                    count++;
                }

                // If more lines exist, print another page.
                if (line != null)
                    ev.HasMorePages = true;
                else
                    ev.HasMorePages = false;
            }

如果要打印图片:

        private static void PicturePrintDocument_PrintPage(object sender, PrintPageEventArgs e)
        {
            FileStream fs = File.OpenRead(filePath);
            int filelength = 0;
            filelength = (int)fs.Length; //获得文件长度 
            Byte[] image = new Byte[filelength]; //建立一个字节数组 
            fs.Read(image, 0, filelength); //按字节流读取 
            Image result = Image.FromStream(fs);
            fs.Close();
            e.Graphics.DrawImage(result, 0, 0);  //img大小
                                                 //e.Graphics.DrawString(TicCode, DrawFont, brush, 600, 600); //绘制字符串
            e.HasMorePages = false;
        }

打印 PDF

这种方式直接绕过打印机驱动,可能会导致明明已经在队列中,但是还不能打印出来。
file

如果所示,明明显示已经打印了,实际上没有打印。

    #region pdf

    /// <summary>
    /// This function gets the pdf file name.
    /// This function opens the pdf file, gets all its bytes & send them to print.
    /// </summary>
    /// <param name="szPrinterName">Printer Name</param>
    /// <param name="szFileName">Pdf File Name</param>
    /// <returns>true on success, false on failure</returns>
    public static bool SendFileToPrinter(string pdfFileName)
    {
        try
        {
            #region Get Connected Printer Name
            PrintDocument pd = new PrintDocument();
            StringBuilder dp = new StringBuilder(256);
            int size = dp.Capacity;
            pd.PrinterSettings.PrinterName = "Deli DL-888D";
            #endregion Get Connected Printer Name

            // Open the PDF file.
            using FileStream fs = new FileStream(pdfFileName, FileMode.Open);
            // Create a BinaryReader on the file.
            BinaryReader br = new BinaryReader(fs);
            Byte[] bytes = new Byte[fs.Length];
            bool success = false;
            // Unmanaged pointer.
            IntPtr ptrUnmanagedBytes = new IntPtr(0);
            int nLength = Convert.ToInt32(fs.Length);
            // Read contents of the file into the array.
            bytes = br.ReadBytes(nLength);
            // Allocate some unmanaged memory for those bytes.
            ptrUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
            // Copy the managed byte array into the unmanaged array.
            Marshal.Copy(bytes, 0, ptrUnmanagedBytes, nLength);
            // Send the unmanaged bytes to the printer.
            success = SendBytesToPrinter(pd.PrinterSettings.PrinterName, ptrUnmanagedBytes, (uint)nLength);
            // Free the unmanaged memory that you allocated earlier.
            Marshal.FreeCoTaskMem(ptrUnmanagedBytes);
            return success;
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }
    }

    /// <summary>
    /// This function gets the printer name and an unmanaged array of bytes, the function sends those bytes to the print queue.
    /// </summary>
    /// <param name="szPrinterName">Printer Name</param>
    /// <param name="pBytes">No. of bytes in the pdf file</param>
    /// <param name="dwCount">Word count</param>
    /// <returns>True on success, false on failure</returns>
    private static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, uint dwCount)
    {
        try
        {
            int dwError = 0;
            uint dwWritten = 0;
            SafeHPRINTER hPrinter;
            DOC_INFO_1 di = new DOC_INFO_1();
            bool success = false; // Assume failure unless you specifically succeed.

            di.pDocName = "PDF Document";
            di.pDatatype = "RAW";

            // Open the printer.
            if (OpenPrinter(szPrinterName.Normalize(), out hPrinter))
            {
                // Start a document.
                if (StartDocPrinter(hPrinter, 1, di) > 0)
                {
                    // 如果有多页,则需要循环打印页
                    // Start a page.
                    if (StartPagePrinter(hPrinter))
                    {
                        // Write the bytes.
                        success = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);

                        EndPagePrinter(hPrinter);
                    }

                    EndDocPrinter(hPrinter);
                }

                ClosePrinter(hPrinter);
            }

            // If print did not succeed, GetLastError may give more information about the failure.
            if (success == false)
            {
                dwError = Marshal.GetLastWin32Error();
            }
            return success;
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }
    }
    #endregion

自定义纸张大小

                        var parper = new PaperSize("Custom", _printOption.CustomSize.Width, _printOption.CustomSize.Height)
                        {
                            PaperName = "自定义",
                            RawKind = (int)PaperKind.Custom
                        };
                        pd.DefaultPageSettings.PaperSize = parper;

如果需要替换默认配置:

                        pd.PrinterSettings.DefaultPageSettings.PaperSize = parper;

打印机打印配置示例

    public class PrintOption
    {
        /// <summary>
        /// 是否彩色打印
        /// </summary>
        public bool? Color { get; set; }

        /// <summary>
        /// 页边距
        /// </summary>
        public Margins? Margins { get; set; }

        /// <summary>
        /// 打印纸张大小名称
        /// </summary>
        public string? PaperName { get; set; }

        /// <summary>
        /// 自定义纸张大小
        /// </summary>
        public PageSize? CustomSize { get; set; }

        /// <summary>
        /// 打印方向设置为横向
        /// </summary>
        public bool? Landscape { get; set; }

        /// <summary>
        /// 要打印多少份
        /// </summary>
        public int Count { get; set; }
        public class PageSize
        {
            public int Width { get; set; }
            public int Height { get; set; }
        }
    }
            void BuildOption(PrintDocument pd)
            {
                if (_printOption != null)
                {
                    if (_printOption.Color != null)
                        pd.PrinterSettings.DefaultPageSettings.Color = _printOption.Color.GetValueOrDefault();
                    if (_printOption.Landscape != null)
                        pd.PrinterSettings.DefaultPageSettings.Landscape = _printOption.Landscape.GetValueOrDefault();
                    if (_printOption.Margins != null)
                        pd.PrinterSettings.DefaultPageSettings.Margins = _printOption.Margins;
                    if (_printOption.CustomSize != null)
                    {
                        var parper = new PaperSize("Custom", _printOption.CustomSize.Width, _printOption.CustomSize.Height)
                        {
                            PaperName = "自定义",
                            RawKind = (int)PaperKind.Custom
                        };
                        pd.DefaultPageSettings.PaperSize = parper;
                    }
                    else if (_printOption.PaperName != null)
                    {
                        for (int i = 0; i < pd.PrinterSettings.PaperSizes.Count; i++)
                        {
                            if (pd.PrinterSettings.PaperSizes[i].PaperName == _printOption.PaperName)
                            {
                                pd.PrinterSettings.DefaultPageSettings.PaperSize = pd.PrinterSettings.PaperSizes[i];
                                break;
                            }
                        }

                    }
                }
            }
        }

痴者工良

高级程序员劝退师

文章评论