有关LocalReport、DeviceInfo和PrintDocument的内容已经介绍得差不多了,稍后会给出一个继承自System.Drawing.Printing.PrintDocument的组件EMFStreamPrintDocument。但是现在,来看一下如何进行自定义纸张票据打印时的页面设置。页面设置窗体如下图所示:
如何添加、删除自定义大小的纸张、枚举系统的打印机?以前在博客园的一篇随笔中参加过讨论,见http://wormday.cnblogs.com/archive/2005/12/22/302635.aspx。当然还是使用Win32 API,以下是我封装的一个关于打印机控制的类[以前用VB实现过比这个类还要多的关于打印机控制的功能,但是在C#中感到还是挺困难的,所以这次只给出了够用的功能:获取当前指定打印机的状态、删除已经存在的自定义纸张、指定的打印机设置以mm为单位的自定义纸张(Form)、获取本地打印机列表、获取本机的默认打印机名称、设置默认打印机、判断打印机是否在系统可用的打印机列表中、判断表单是否在指定的打印机所支持的纸张列表中、判断指定纸张的宽度和高度和在文本框中指定的宽度和高度是否匹配、英尺到厘米的转换]:
using System.Text;
using System.Runtime.InteropServices;
using System.Security;
using System.ComponentModel;
using System.Drawing.Printing;
namespace RDLCReport
{
public class Printer
{
private Printer()
{
}
API声明
internal static int GetPrinterStatusInt(string PrinterName)
{
int intRet = 0;
IntPtr hPrinter;
structPrinterDefaults defaults = new structPrinterDefaults();
if (OpenPrinter(PrinterName, out hPrinter, ref defaults))
{
int cbNeeded = 0;
bool bolRet = GetPrinter(hPrinter, 2, IntPtr.Zero, 0, out cbNeeded);
if (cbNeeded > 0)
{
IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
bolRet = GetPrinter(hPrinter, 2, pAddr, cbNeeded, out cbNeeded);
if (bolRet)
{
PRINTER_INFO_2 Info2 = new PRINTER_INFO_2();
Info2 = (PRINTER_INFO_2)Marshal.PtrToStructure(pAddr, typeof(PRINTER_INFO_2));
intRet = System.Convert.ToInt32(Info2.Status);
}
Marshal.FreeHGlobal(pAddr);
}
ClosePrinter(hPrinter);
}
return intRet;
}
internal static PRINTER_INFO_2[] EnumPrintersByFlag(PrinterEnumFlags Flags)
{
uint cbNeeded = 0;
uint cReturned = 0;
bool ret = EnumPrinters( PrinterEnumFlags.PRINTER_ENUM_LOCAL, null, 2, IntPtr.Zero, 0, ref cbNeeded, ref cReturned);
IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
ret = EnumPrinters(PrinterEnumFlags.PRINTER_ENUM_LOCAL, null, 2, pAddr, cbNeeded, ref cbNeeded, ref cReturned);
if (ret)
{
PRINTER_INFO_2[] Info2 = new PRINTER_INFO_2[cReturned];
int offset = pAddr.ToInt32();
for (int i = 0; i < cReturned; i++)
{
Info2[i].pServerName = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pPrinterName = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pShareName = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pPortName = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pDriverName = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pComment = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pLocation = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pDevMode = Marshal.ReadIntPtr(new IntPtr(offset));
offset += 4;
Info2[i].pSepFile = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pPrintProcessor = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pDatatype = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pParameters = Marshal.PtrToStringAuto(Marshal.ReadIntPtr(new IntPtr(offset)));
offset += 4;
Info2[i].pSecurityDescriptor = Marshal.ReadIntPtr(new IntPtr(offset));
offset += 4;
Info2[i].Attributes = (uint )Marshal.ReadIntPtr(new IntPtr(offset));
offset += 4;
Info2[i].Priority = (uint)Marshal.ReadInt32(new IntPtr(offset));
offset += 4;
Info2[i].DefaultPriority = (uint)Marshal.ReadInt32(new IntPtr(offset));
offset += 4;
Info2[i].StartTime = (uint)Marshal.ReadInt32(new IntPtr(offset));
offset += 4;
Info2[i].UntilTime = (uint)Marshal.ReadInt32(new IntPtr(offset));
offset += 4;
Info2[i].Status = (uint)Marshal.ReadInt32(new IntPtr(offset));
offset += 4;
Info2[i].cJobs = (uint)Marshal.ReadInt32(new IntPtr(offset));
offset += 4;
Info2[i].AveragePPM = (uint)Marshal.ReadInt32(new IntPtr(offset));
offset += 4;
}
Marshal.FreeHGlobal(pAddr);
return Info2;
}
else
{
return new PRINTER_INFO_2[0];
}
}
/// <summary>
/// 获取当前指定打印机的状态
/// </summary>
/// <param name="PrinterName">打印机名称</param>
/// <returns>打印机状态描述</returns>
public static string GetPrinterStatus(string PrinterName)
{
int intValue = GetPrinterStatusInt(PrinterName);
string strRet = string.Empty;
switch (intValue)
{
case 0:
strRet = "准备就绪(Ready)";
break;
case 0x00000200:
strRet = "忙(Busy)";
break;
case 0x00400000:
strRet = "门被打开(Printer Door Open)";
break;
case 0x00000002:
strRet = "错误(Printer Error)";
break;
case 0x0008000:
strRet = "正在初始化(Initializing)";
break;
case 0x00000100:
strRet = "正在输入或输出(I/O Active)";
break;
case 0x00000020:
strRet = "手工送纸(Manual Feed)";
break;
case 0x00040000:
strRet = "无墨粉(No Toner)";
break;
case 0x00001000:
strRet = "不可用(Not Available)";
break;
case 0x00000080:
strRet = "脱机(Off Line)";
break;
case 0x00200000:
strRet = "内存溢出(Out of Memory)";
break;
case 0x00000800:
strRet = "输出口已满(Output Bin Full)";
break;
case 0x00080000:
strRet = "当前页无法打印(Page Punt)";
break;
case 0x00000008:
strRet = "塞纸(Paper Jam)";
break;
case 0x00000010:
strRet = "打印纸用完(Paper Out)";
break;
case 0x00000040:
strRet = "纸张问题(Page Problem)";
break;
case 0x00000001:
strRet = "暂停(Paused)";
break;
case 0x00000004:
strRet = "正在删除(Pending Deletion)";
break;
case 0x00000400:
strRet = "正在打印(Printing)";
break;
case 0x00004000:
strRet = "正在处理(Processing)";
break;
case 0x00020000:
strRet = "墨粉不足(Toner Low)";
break;
case 0x00100000:
strRet = "需要用户干预(User Intervention)";
break;
case 0x20000000:
strRet = "等待(Waiting)";
break;
case 0x00010000:
strRet = "正在准备(Warming Up)";
break;
default:
strRet = "未知状态(Unknown Status)";
break;
}
return strRet;
}
/// <summary>
/// 删除已经存在的自定义纸张
/// </summary>
/// <param name="PrinterName">打印机名称</param>
/// <param name="PaperName">纸张名称</param>
public static void DeleteCustomPaperSize(string PrinterName, string PaperName)
{
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);
ClosePrinter(hPrinter);
}
catch
{
Pub.WinForm.Msg.Warning("删除自定义纸张时发生错误!");
}
}
}
/// <summary>
/// 指定的打印机设置以mm为单位的自定义纸张(Form)
/// </summary>
/// <param name="PrinterName">打印机名称</param>
/// <param name="PaperName">Form名称</param>
/// <param name="WidthInMm">以mm为单位的宽度</param>
/// <param name="HeightInMm">以mm为单位的高度</param>
public static void AddCustomPaperSize(string PrinterName, string PaperName, float WidthInMm, float HeightInMm)
{
if (PlatformID.Win32NT == Environment.OSVersion.Platform)
{
const int PRINTER_ACCESS_USE = 0x00000008;
const int PRINTER_ACCESS_ADMINISTER = 0x00000004;
const int FORM_PRINTER = 0x00000002;
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
{
//如果Form存在删除之
DeleteForm(hPrinter, PaperName);
//创建并初始化FORM_INFO_1
FormInfo1 formInfo = new FormInfo1();
formInfo.Flags = 0;
formInfo.pName = PaperName;
formInfo.Size.width = (int)(WidthInMm * 1000.0);
formInfo.Size.height = (int)(HeightInMm * 1000.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("向打印机 {1} 添加自定义纸张 {0} 失败!错误代号:{2}",
PaperName, PrinterName, GetLastError());
throw new ApplicationException(strBuilder.ToString());
}
//初始化
const int DM_OUT_BUFFER = 2;
const int DM_IN_BUFFER = 8;
structDevMode devMode = new structDevMode();
IntPtr hPrinterInfo, hDummy;
PRINTER_INFO_9 printerInfo;
printerInfo.pDevMode = IntPtr.Zero;
int iPrinterInfoSize, iDummyInt;
int iDevModeSize = DocumentProperties(IntPtr.Zero, hPrinter, PrinterName, IntPtr.Zero, IntPtr.Zero, 0);
if (iDevModeSize < 0)
throw new ApplicationException("无法取得DEVMODE结构的大小!");
//分配缓冲
IntPtr hDevMode = Marshal.AllocCoTaskMem(iDevModeSize + 100);
//获取DEV_MODE指针
int iRet = DocumentProperties(IntPtr.Zero, hPrinter, PrinterName, hDevMode, IntPtr.Zero, DM_OUT_BUFFER);
if (iRet < 0)
throw new ApplicationException("无法获得DEVMODE结构!");
//填充DEV_MODE
devMode = (structDevMode)Marshal.PtrToStructure(hDevMode, devMode.GetType());
devMode.dmFields = 0x10000;
//FORM名称
devMode.dmFormName = PaperName;
Marshal.StructureToPtr(devMode, hDevMode, true);
iRet = DocumentProperties(IntPtr.Zero, hPrinter, PrinterName,
printerInfo.pDevMode, printerInfo.pDevMode, DM_IN_BUFFER | DM_OUT_BUFFER);
if (iRet < 0)
throw new ApplicationException("无法为打印机设定打印方向!");
GetPrinter(hPrinter, 9, IntPtr.Zero, 0, out iPrinterInfoSize);
if (iPrinterInfoSize == 0)
throw new ApplicationException("调用GetPrinter方法失败!");
hPrinterInfo = Marshal.AllocCoTaskMem(iPrinterInfoSize + 100);
bool bSuccess = GetPrinter(hPrinter, 9, hPrinterInfo, iPrinterInfoSize, out iDummyInt);
if (!bSuccess)
throw new ApplicationException("调用GetPrinter方法失败!");
printerInfo = (PRINTER_INFO_9)Marshal.PtrToStructure(hPrinterInfo, printerInfo.GetType());
printerInfo.pDevMode = hDevMode;
Marshal.StructureToPtr(printerInfo, hPrinterInfo, true);
bSuccess = SetPrinter(hPrinter, 9, hPrinterInfo, 0);
if (!bSuccess)
throw new Win32Exception(Marshal.GetLastWin32Error(), "调用SetPrinter方法失败,无法进行打印机设置!");
SendMessageTimeout(
new IntPtr(HWND_BROADCAST),
WM_SETTINGCHANGE,
IntPtr.Zero,
IntPtr.Zero,
Printer.SendMessageTimeoutFlags.SMTO_NORMAL,
1000,
out hDummy);
}
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)(WidthInMm * 1000.0);
pDevMode.dmPaperLength = (short)(HeightInMm * 1000.0);
ResetDC(hDC, ref pDevMode);
DeleteDC(hDC);
}
}
}
/// <summary>
/// 获取本地打印机列表
/// 可以通过制定参数获取网络打印机
/// </summary>
/// <returns>打印机列表</returns>
public static System.Collections.ArrayList GetPrinterList()
{
System.Collections.ArrayList alRet = new System.Collections.ArrayList();
PRINTER_INFO_2[] Info2 = EnumPrintersByFlag(PrinterEnumFlags.PRINTER_ENUM_LOCAL);
for (int i = 0; i < Info2.Length; i++)
{
alRet.Add(Info2[i].pPrinterName);
}
return alRet;
}
/// <summary>
/// 获取本机的默认打印机名称
/// </summary>
/// <returns>默认打印机名称</returns>
public static string GetDeaultPrinterName()
{
StringBuilder dp = new StringBuilder(256);
int size = dp.Capacity;
if (GetDefaultPrinter(dp, ref size))
{
return dp.ToString();
}
else
{
int rc = GetLastError();
Pub.WinForm.Msg.Warning("获取默认打印机失败!错误代号:" + rc.ToString());
return string.Empty;
}
}
/// <summary>
/// 设置默认打印机
/// </summary>
/// <param name="PrinterName">可用的打印机名称</param>
public static void SetPrinterToDefault(string PrinterName)
{
SetDefaultPrinter(PrinterName);
}
///// <summary>
///// 判断打印机是否在系统可用的打印机列表中
///// </summary>
///// <param name="PrinterName">打印机名称</param>
///// <returns>是:在;否:不在</returns>
public static bool PrinterInList(string PrinterName)
{
bool bolRet = false;
System.Collections.ArrayList alPrinters = GetPrinterList();
for (int i = 0; i < alPrinters.Count; i++)
{
if (PrinterName == alPrinters[i].ToString())
{
bolRet = true;
break;
}
}
alPrinters.Clear();
alPrinters = null;
return bolRet;
}
///// <summary>
///// 判断表单是否在指定的打印机所支持的纸张列表中
///// </summary>
///// <param name="PrinterName">打印机名称</param>
///// <param name="PaperName">纸张名称</param>
///// <returns>是:在;否:不在</returns>
public static bool FormInPrinter(string PrinterName, string PaperName)
{
bool bolRet = false;
System.Drawing.Printing.PrintDocument pd = new System.Drawing.Printing.PrintDocument();
pd.PrinterSettings.PrinterName = PrinterName;
foreach (System.Drawing.Printing.PaperSize ps in pd.PrinterSettings.PaperSizes)
{
if (ps.PaperName == PaperName)
{
bolRet = true;
break;
}
}
pd.Dispose();
return bolRet;
}
/// <summary>
/// 判断指定纸张的宽度和高度和在文本框中指定的宽度和高度是否匹配
/// </summary>
/// <param name="PrinterName">打印机名称</param>
/// <param name="FormName">表单名称</param>
/// <param name="Width">宽度</param>
/// <param name="Height">高度</param>
/// <returns></returns>
public static bool FormSameSize(string PrinterName, string FormName, decimal Width, decimal Height)
{
bool bolRet = false;
System.Drawing.Printing.PrintDocument pd = new System.Drawing.Printing.PrintDocument();
pd.PrinterSettings.PrinterName = PrinterName;
foreach (System.Drawing.Printing.PaperSize ps in pd.PrinterSettings.PaperSizes)
{
if (ps.PaperName == FormName)
{
decimal decWidth = FromInchToCM(System.Convert.ToDecimal(ps.Width));
decimal decHeight = FromInchToCM(System.Convert.ToDecimal(ps.Height));
//只要整数位相同即认为是同一纸张,毕竟inch到cm的转换并不能整除
if (Pub.MathEx.Round(decWidth, 0) == Pub.MathEx.Round(Width, 0) && Pub.MathEx.Round(decHeight, 0) == Pub.MathEx.Round(Height, 0))
bolRet = true;
break;
}
}
pd.Dispose();
return bolRet;
}
/// <summary>
/// 英尺到厘米的转换
/// 米国人用的是英尺,中国人用的是厘米
/// 1 inch = 2.5400 cm
/// </summary>
/// <param name="inch">英尺数</param>
/// <returns>厘米数,两位小数</returns>
public static decimal FromInchToCM(decimal inch)
{
return Math.Round((System.Convert.ToDecimal((inch / 100)) * System.Convert.ToDecimal(2.5400)), 2);
}
}
}
页面设置窗体由报表浏览器窗体ReportViewer调用,使用以下结构的XML文件存储针对每个报表的页面设置:
<ReportSettings>
<采购订单>
<ReportName>采购订单</ReportName>
<PrinterName>EPSON LQ-1600KIII</PrinterName>
<PaperName>Test</PaperName>
<PageWidth>8.00</PageWidth>
<PageHeight>8.00</PageHeight>
<MarginTop>0.2</MarginTop>
<MarginBottom>0.2</MarginBottom>
<MarginLeft>0.2</MarginLeft>
<MarginRight>0.2</MarginRight>
<Orientation>横向</Orientation>
</采购订单>
</ReportSettings>
当然,这种格式的XML配置文件是用DataSet来读写更新的,因为这种方法比较简单。
页面设置窗体上有一个关于打印方向的设置,在(六)中有一个问题忘记阐述了,就是关于打印方向的。因为DeviceInfo结构中并没有打印方向的设置,所以在生成DeviceInfo结构的字符串时,应该根据打印方向来设置DeviceInfo结构的页高和页宽,见(五)中给出的EMFDeviceInfo类的属性DeviceInfoString。放在这里补充说一下。
完整的页面设置窗体的代码下载
相关随笔:
RDLC报表(一)
RDLC报表(二)
RDLC报表(三)
RDLC报表(四)
RDLC报表(五)
RDLC报表(六)