背景:
I searched many libraries, but either they charged fees or were too outdated to be used.
After extensive testing, I wrote the relevant code for the printer.
The implemented code does not rely on third-party libraries.
Core Code
Import these two libraries:
System.Drawing.Printing
Vanara.PInvoke.Printing
These two libraries are used to utilize the winspool.drv service, which can avoid writing a lot of library function call code.
First, write the basic code:
public class PrinterBase
{
/// <summary>
/// Get the default printer
/// </summary>
/// <returns></returns>
public static string GetDefaultPrinter()
{
// The PrintDocument object will use the default printer by default
PrintDocument print = new PrintDocument();
return print.PrinterSettings.PrinterName;
}
/// <summary>
/// Get all local printers
/// </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;
}
}
}
Simple output for printer properties:
static void Main()
{
var ps = PrinterBase.GetLocalPrinters();
foreach (var name in ps)
{
PrintDocument print = new PrintDocument();
print.PrinterSettings.PrinterName = name; // After setting the printer name, PrintDocument will automatically switch to the corresponding printer instance
Console.WriteLine($"Printer Name: {name}");
Console.WriteLine($"Printer Available: {print.PrinterSettings.IsValid}");
Console.WriteLine($"Default Printer: {print.PrinterSettings.IsDefaultPrinter}");
Console.WriteLine($"Is Plotter: {print.PrinterSettings.IsPlotter}");
Console.WriteLine($"Supports Color Printing: {print.PrinterSettings.SupportsColor}");
Console.WriteLine($"Supports Duplex Printing: {print.PrinterSettings.CanDuplex}");
Console.WriteLine($"-------------------------------------------");
}
}
Printer Name: Export as WPS PDF
Printer Available: True
Default Printer: False
Is Plotter: False
Supports Color Printing: True
Supports Duplex Printing: False
-------------------------------------------
Printer Name: OneNote
Printer Available: True
Default Printer: False
Is Plotter: False
Supports Color Printing: True
Supports Duplex Printing: False
-------------------------------------------
Printer Name: Microsoft XPS Document Writer
Printer Available: True
Default Printer: False
Is Plotter: False
Supports Color Printing: True
Supports Duplex Printing: False
-------------------------------------------
Printer Name: Microsoft Print to PDF
Printer Available: True
Default Printer: False
Is Plotter: False
Supports Color Printing: True
Supports Duplex Printing: False
-------------------------------------------
Printer Name: Fax
Printer Available: True
Default Printer: False
Is Plotter: False
Supports Color Printing: False
Supports Duplex Printing: False
-------------------------------------------
Printer Name: Deli DL-888D
Printer Available: True
Default Printer: True
Is Plotter: False
Supports Color Printing: False
Supports Duplex Printing: False
-------------------------------------------
For information properties, you can refer to: PrinterSettings
https://learn.microsoft.com/zh-cn/dotnet/api/system.drawing.printing.printersettings?view=dotnet-plat-ext-7.0
If you want to get information like shown in the image below, it can be a bit cumbersome.
Getting Printer Status
https://learn.microsoft.com/zh-cn/windows/win32/printdocs/printer-info-2
Printer status. This member can be any reasonable combination of the following values.
Using the encapsulated framework, it may not necessarily retrieve real-time information, and I found that WPS has this issue as well.
If a printer is unplugged, the program cannot detect the actual situation of the printer.
There is also a problem where the printer status value can be a combination of various enumerations.
Enumeration values list:
| Value | Meaning |
| :------------------------------- | :--------------------------------------- |
| PRINTER_STATUS_BUSY | The printer is busy. |
| PRINTER_STATUS_DOOR_OPEN | The printer door is open. |
| PRINTER_STATUS_ERROR | The printer is in an error state. |
| PRINTER_STATUS_INITIALIZING | The printer is initializing. |
| PRINTER_STATUS_IO_ACTIVE | The printer is in active input/output state. |
| PRINTER_STATUS_MANUAL_FEED | The printer is in manual feed mode. |
| PRINTER_STATUS_NO_TONER | The printer is out of toner. |
| PRINTER_STATUS_NOT_AVAILABLE | The printer is not available for printing. |
| PRINTER_STATUS_OFFLINE | The printer is offline. |
| PRINTER_STATUS_OUT_OF_MEMORY | The printer is out of memory. |
| PRINTER_STATUS_OUTPUT_BIN_FULL | The printer's output bin is full. |
| PRINTER_STATUS_PAGE_PUNT | The printer cannot print the current page. |
| PRINTER_STATUS_PAPER_JAM | Paper is jammed in the printer. |
| PRINTER_STATUS_PAPER_OUT | The printer is out of paper. |
| PRINTER_STATUS_PAPER_PROBLEM | The printer has a paper-related issue. |
| PRINTER_STATUS_PAUSED | The printer has been paused. |
| PRINTER_STATUS_PENDING_DELETION | The printer is being deleted. |
| PRINTER_STATUS_POWER_SAVE | The printer is in power-saving mode. |
| PRINTER_STATUS_PRINTING | The printer is printing. |
| PRINTER_STATUS_PROCESSING | The printer is processing print jobs. |
| PRINTER_STATUS_SERVER_UNKNOWN | The printer status is unknown. |
| PRINTER_STATUS_TONER_LOW | The toner is low on the printer cartridge. |
| PRINTER_STATUS_USER_INTERVENTION | The printer has an error requiring user action. |
| PRINTER_STATUS_WAITING | The printer is waiting. |
| PRINTER_STATUS_WARMING_UP | The printer is warming up. |
#region Printer Status
// Get the printer status code
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;
}
// Convert printer status code to corresponding message
private static string PrinterStatusToMessage(WinSpool.PRINTER_STATUS intStatusCodeValue)
{
if (intStatusCodeValue == 0)
{
return "The printer is ready.";
}
switch (intStatusCodeValue)
{
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_BUSY) == PRINTER_STATUS.PRINTER_STATUS_BUSY:
return "The printer is busy.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_DOOR_OPEN) == PRINTER_STATUS.PRINTER_STATUS_DOOR_OPEN:
return "The printer door is open.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_ERROR) == PRINTER_STATUS.PRINTER_STATUS_ERROR:
return "The printer is in an error state.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_INITIALIZING) == PRINTER_STATUS.PRINTER_STATUS_INITIALIZING:
return "The printer is initializing.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_IO_ACTIVE) == PRINTER_STATUS.PRINTER_STATUS_IO_ACTIVE:
return "The printer is in an active input/output state.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_MANUAL_FEED) == PRINTER_STATUS.PRINTER_STATUS_MANUAL_FEED:
return "The printer is in manual feed mode.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_NOT_AVAILABLE) == PRINTER_STATUS.PRINTER_STATUS_NOT_AVAILABLE:
return "The printer is not available for printing.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_NO_TONER) == PRINTER_STATUS.PRINTER_STATUS_NO_TONER:
return "The printer is out of toner.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_OFFLINE) == PRINTER_STATUS.PRINTER_STATUS_OFFLINE:
return "The printer is offline.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_OUTPUT_BIN_FULL) == PRINTER_STATUS.PRINTER_STATUS_OUTPUT_BIN_FULL:
return "The printer's output bin is full.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_OUT_OF_MEMORY) == PRINTER_STATUS.PRINTER_STATUS_OUT_OF_MEMORY:
return "The printer is out of memory.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAGE_PUNT) == PRINTER_STATUS.PRINTER_STATUS_PAGE_PUNT:
return "The printer cannot print the current page.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAPER_JAM) == PRINTER_STATUS.PRINTER_STATUS_PAPER_JAM:
return "The paper is jammed in the printer.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAPER_OUT) == PRINTER_STATUS.PRINTER_STATUS_PAPER_OUT:
return "The printer is out of paper.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAPER_PROBLEM) == PRINTER_STATUS.PRINTER_STATUS_PAPER_PROBLEM:
return "The printer has a paper issue.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PAUSED) == PRINTER_STATUS.PRINTER_STATUS_PAUSED:
return "The printer is paused.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PENDING_DELETION) == PRINTER_STATUS.PRINTER_STATUS_PENDING_DELETION:
return "The printer is being deleted.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_POWER_SAVE) == PRINTER_STATUS.PRINTER_STATUS_POWER_SAVE:
return "The printer is in power save mode.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PRINTING) == PRINTER_STATUS.PRINTER_STATUS_PRINTING:
return "The printer is printing.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_PROCESSING) == PRINTER_STATUS.PRINTER_STATUS_PROCESSING:
return "The printer is processing a print job.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_SERVER_OFFLINE) == PRINTER_STATUS.PRINTER_STATUS_SERVER_OFFLINE:
return "The printer server is offline.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_SERVER_UNKNOWN) == PRINTER_STATUS.PRINTER_STATUS_SERVER_UNKNOWN:
return "The printer status is unknown.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_TONER_LOW) == PRINTER_STATUS.PRINTER_STATUS_TONER_LOW:
return "The printer is low on toner.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_USER_INTERVENTION) == PRINTER_STATUS.PRINTER_STATUS_USER_INTERVENTION:
return "The printer has an error requiring user intervention.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_WAITING) == PRINTER_STATUS.PRINTER_STATUS_WAITING:
return "The printer is waiting.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_WARMING_UP) == PRINTER_STATUS.PRINTER_STATUS_WARMING_UP:
return "The printer is warming up.";
case PRINTER_STATUS status when (status & PRINTER_STATUS.PRINTER_STATUS_DRIVER_UPDATE_NEEDED) == PRINTER_STATUS.PRINTER_STATUS_DRIVER_UPDATE_NEEDED:
return "The printer needs a driver update.";
}
return "Unknown";
#endregion
Since the status codes can be combined, only one error is returned here; using if statements would be better.
/// <summary>
/// Get the status information of the specified printer
/// </summary>
/// <remarks>Opening and closing the printer takes some time, which may be slow</remarks>
/// <param name="printerName"></param>
/// <returns></returns>
public static string GetPrinterStatusMessage(string printerName)
{
var code = GetPrinterStatusCodeInt(printerName);
var message = PrinterStatusToMessage(code);
return message;
}
Show Printer Properties
When you "click on properties", it opens the properties window for the current printer.
.
The sample code is as follows:
/// <summary>
/// Pop up the printer properties window
/// </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);
}
}
Printing Files
To print files, you need to use PrintDocument. The type of files that can be printed depends on the PrintPage
event. The core code for printing is as follows:
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();
}
}
If you want to print text:
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;
}
If you want to print an image:
private static void PicturePrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
FileStream fs = File.OpenRead(filePath);
int filelength = 0;
filelength = (int)fs.Length; // Obtain file length
Byte[] image = new Byte[filelength]; // Create a byte array
fs.Read(image, 0, filelength); // Read byte stream
Image result = Image.FromStream(fs);
fs.Close();
e.Graphics.DrawImage(result, 0, 0); // img size
// e.Graphics.DrawString(TicCode, DrawFont, brush, 600, 600); // Draw string
e.HasMorePages = false;
}
Printing PDF
This method directly bypasses the printer driver, which may lead to the situation where it appears to be in the queue but is not actually printing.
As shown, it clearly indicates that it has been printed, but in reality, it has not been printed.
#region pdf
/// <summary>
/// This function gets the pdf file name.
/// This function opens the pdf file, gets all its bytes & sends 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)
{
// If there are multiple pages, loop through printing pages
// 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
Custom Paper Size
var parper = new PaperSize("Custom", _printOption.CustomSize.Width, _printOption.CustomSize.Height)
{
PaperName = "自定义",
RawKind = (int)PaperKind.Custom
};
pd.DefaultPageSettings.PaperSize = parper;
If default configuration needs to be replaced:
pd.PrinterSettings.DefaultPageSettings.PaperSize = parper;
Printer Printing Configuration Example
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;
}
}
}
}
}
}
文章评论