内容目录
背景:
查找了很多库,要么收费,要么太旧用不了。
经过大量测试,写了打印机的相关代码。
实现的代码不依赖于第三方库。
核心代码
引入这两个库:
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
如果要获取像下图中的信息,会有些麻烦。
获取打印机状态
https://learn.microsoft.com/zh-cn/windows/win32/printdocs/printer-info-2
打印机状态。 此成员可以是以下值的任何合理组合。
使用封装的框架,但是不一定可以实时获取到,发现 wps 也有这个问题。
就是把打印机拔下来了,程序无法检测到打印机的实际情况。
而且还有个问题,打印机状态值可以是枚举的各种组合,这个问题
枚举值列表:
值 | 含义 |
---|---|
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;
弹出打印机属性
打击 “属性时”,弹出当前打印机的属性窗口。
示例代码如下:
/// <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
这种方式直接绕过打印机驱动,可能会导致明明已经在队列中,但是还不能打印出来。
如果所示,明明显示已经打印了,实际上没有打印。
#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;
}
}
}
}
}
}
文章评论