C#程序动态设定打印选项 [Programmatically selecting complex printer options in C#]
2013-11-18 16:37
537 查看
Programmatically selecting complex printer options in C#
We print out a full-color eight page customized flyer for every one of our orders. As soon as the order is donebeing processed, our internal processing application generates the flyer and shoots it off to one of the copier machines. The copier machine prints out the flyer (duplex), folds it, and staples it. This all happens silently and automatically; the employee
does not have to twiddle with printer configuration or driver settings.
Getting this to happen automatically, however, was not easy. If you’re used to using thePrinterSettings and PrintDocument classes
in Windows Forms, you’ll know that you have some options like a Duplex option available. But in our case, using that Duplex property
seemed to make no difference in the printed output; the ol’ copier stubbornly went simplex every single time. It Simply Did Not Work. Not to mention that the .NET-native API doesn’t even begin to provide support for specifying paper folding, saddle stitching,
and stapling. In a quest to find out how I could automate the selection of all these settings, I learned a bit more about printer drivers on Windows than I cared to know.
To get down and dirty with printer settings, I had to get familiar with DEVMODE.
Being a .NET weenie who really doesn’t know his way around the Windows API very well, discovering this was progress; this structure exposes some of the options that I needed (such as collation), but it still didn’t provide any programmatic way to access folding
and stapling settings.
Let’s go into the Printers section of the Control Panel and look at the Printing Preferences for one of these copiers. It turns out that this is a good way to be able to tell where a configuration setting lives. Some of the universal, standard settings are
displayed on the “Layout” and “Paper/Quality” tabs. But if I want to get to any of the fancy features such as folding or stapling, well, they’re specific to our unique printer driver (and indeed, they’re exposed on a custom tab called “Fiery Printing” that
the printer driver provides). There’s approximately zero documentation out there for configuring Fiery printer drivers programmatically, so how the hell was I going to communicate with this black box?
The Layout and Paper/Quality tabs can be configured by setting fields of the DEVMODE structure to documented values. But many of the Advanced "Printer Features" live in a driver-specific, undocumented part of memory just beyond the DEVMODE structure.
I’m not sure if my solution is genius, expected, or insane, but it’s been working for over a year now. The secret is a curious little member of the DEVMODE structure
calleddmDriverExtra. MSDN has the following to say about this little member:
Contains the number of bytes of private driver-data that follow this structure. If a device driver does not use device-specific information, set this member to zero.
Private driver data, I thought. How
interesting. It’s perfectly logical to assume that since the stapling and folding features are unique to this printer, then the configuration for those features is stored in some unknown format. That data lives in a block of memory at the end of the DEVMODE structure.
The size of that block of memory is the value given in thedmDriverExtra field.
Here’s a crazy idea, I continued. What
if I configured the default printer settings via the Printer Preferences pane in the Control Panel to the exact settings that I want the fliers to print with. Then, I’ll write a program that dumps the default DEVMODE structure
and its private data for the printer to a file. Then, I can revert the default printer settings in the Control Panel back to normal.
When I want to print the flyer, I’ll just obtain the default DEVMODE,
overwrite its spot in memory with the version that I had saved to disk, and then send the document to the printer. That way, I can specify those proprietary folding and stapling settings without actually knowing exactly how they’re defined in memory!
The printer-specific configuration data lives just beyond the DEVMODE structure.
It turns out that this ended up working quite well. Obviously, things will blow up if we ever change the version of the printer driver that’s installed on the server (since they’ll probably have changed the layout of their private data section). Luckily, though,
we don’t really plan on changing things once they’re working.
Dumping the DEVMODE
So, first I went into the Control Panel and selected the duplexing, folding, and stapling options that I wanted in the print driver-supplied tab. Then I ran a quick and dirty console application like the following (I swiped the P/Invoke version of DEVMODE from pinvoke.net:class Program2 { #region P/Invoke [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalFree(IntPtr handle); [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalLock(IntPtr handle); [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalUnlock(IntPtr handle); #endregion #region Constants private const string DUMP_PATH = "DevModeDump.bin"; #endregion static void Main(string[] args) { IntPtr hDevMode; // handle to the DEVMODE IntPtr pDevMode; // pointer to the DEVMODE DEVMODE devMode; // the actual DEVMODE structure PrinterSettings printerSettings; // our flyer's printer settings Fall2008LargeFlyerPrintDocument flyer; // our custom subclass of PrintDocument flyer = new Fall2008LargeFlyerPrintDocument(); flyer.PrintController = new StandardPrintController(); printerSettings = flyer.PrinterSettings; printerSettings.PrinterName = "Fiery X3E"; // Get a handle to a DEVMODE for the default printer settings hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings); // Obtain a lock on the handle and get an actual pointer so Windows won't // move it around while we're futzing with it pDevMode = GlobalLock(hDevMode); // Marshal the memory at that pointer into our P/Invoke version of DEVMODE devMode = (DEVMODE)Marshal.PtrToStructure(pDevMode, typeof(DEVMODE)); // Read the bytes starting at that pointers position in memory into our // file stream using (FileStream fs = new FileStream(DUMP_PATH, FileMode.Create)) { for (int i = 0; i < devMode.dmSize + devMode.dmDriverExtra; ++i) { fs.WriteByte(Marshal.ReadByte(pDevMode, i)); } } // Unlock the handle, we're done futzing around with memory GlobalUnlock(hDevMode); // And to boot, we don't need that DEVMODE anymore, either GlobalFree(hDevMode); } } |
Using the saved DEVMODE for printing
Now what do I do when I want to crank these suckers out? I just take my saved DEVMODE and overwrite the one that I get in memory when I’m about to print. It’s something like this:class Program3 { #region P/Invoke [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalFree(IntPtr handle); [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalLock(IntPtr handle); [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalUnlock(IntPtr handle); #endregion #region Constants private const string DUMP_PATH = "DevModeDump.bin"; #endregion static void Main(string[] args) { IntPtr hDevMode; // a handle to our current DEVMODE IntPtr pDevMode; // a pointer to our current DEVMODE PrinterSettings printerSettings; // our flyer's printer settings Fall2008LargeFlyerPrintDocument flyer; // our custom subclass of PrintDocument byte[] savedDevMode; // will hold the DEVMODE we saved earlier Stream savedDevStream; // used to read the DEVMODE we saved earlier flyer = new Fall2008LargeFlyerPrintDocument(); renderedFlyer.PrintController = new StandardPrintController(); printerSettings = renderedFlyer.PrinterSettings; printerSettings.PrinterName = "Fiery X3E"; // Obtain the current DEVMODE position in memory hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings); // Obtain a lock on the handle and get an actual pointer so Windows won't move // it around while we're futzing with it pDevMode = GlobalLock(hDevMode); // Load the saved DEVMODE that we dumped earlier savedDevStream = new FileStream(DUMP_PATH, FileMode.Open, FileAccess.Read); savedDevMode = new byte[savedDevStream.Length]; savedDevStream.Read(savedDevMode, 0, savedDevMode.Length); savedDevStream.Close(); savedDevStream.Dispose(); // Overwrite our current DEVMODE in memory with the one we saved. // They should be the same size since we haven't like upgraded the OS // or anything. for (int i = 0; i < savedDevMode.Length; ++i) { Marshal.WriteByte(pDevMode, i, savedDevMode[i]); } // We're done futzing GlobalUnlock(hDevMode); // Tell our printer settings to use the one we just overwrote printerSettings.SetHdevmode(hDevMode); // It's copied to our printer settings, so we can free the OS-level one GlobalFree(hDevMode); // Print and we're done! renderedFlyer.Print(); renderedFlyer.Dispose(); } } |
相关文章推荐
- C# 条码标签打印程序,RDLC报表动态显示多条码标签的方法
- C#实现无物理边距真正可打印区域的绘图\打印程序开发
- C#程序实现动态调用DLL的研究
- C#程序实现动态调用DLL的研究(转载)
- C# 设置Excel打印选项及打印excel文档
- C#趣味小程序(6)——动态工具栏
- C#中动态创建控件及事件处理程序
- C#版本的Tribon M3打印程序
- 使用C#的AssemblyResolve事件动态解析加载失败的程序集
- [转载]C#如何实现对外部程序的动态调用
- C#程序实现动态调用DLL的研究(2)
- C# 读取注册表,动态运行程序.
- 在C#中使用PrintDialog可以很方便的实现程序的打印功能。
- C# 实现简单打印(三)-认识打印控件,创建一个带打印功能的程序
- AndroidGUI26:程序中动态设定组件的宽度、高度以及margin等属性
- 为什么装了windows mobile后vs2005下创建C#程序就只有smart device选项了?
- C#程序实现动态调用DLL的研究
- c#开发的程序安装时动态指定windows服务名称
- C# 动态加载程序集信息
- C# 简单的ZEBRA标签打印程序