2014年1月3日星期五

RDLC报表(七)

有关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; 
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, 0out 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, null2, IntPtr.Zero, 0ref cbNeeded, ref cReturned); 
 
            IntPtr pAddr 
= Marshal.AllocHGlobal((int)cbNeeded); 
            ret 
= EnumPrinters(PrinterEnumFlags.PRINTER_ENUM_LOCAL, null2, 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, 1ref 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, 0out 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, nullref 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文件存储针对每个报表的页面设置:
<?xml version="1.0" standalone="yes"?>  
<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报表(六)