在 C#.NET 中存储和调用打印机设置






4.69/5 (8投票s)
本文介绍了如何在 C#.NET 中将任何打印机的打印机设置保存到文件或 ArrayList 中,以便以后调用。
引言
基于 Nicholas Piasecki 的想法
本文介绍了如何在 C#.NET 中将任何打印机的打印机设置保存到文件或 ArrayList 中,以便以后调用。
背景
我一直在开发一个用于设计无线电接收机的 C#.NET 应用程序。
该应用程序具有打印功能,可以打印各种文本、图形和刻度,以便您查看您正在收听的内容。
打印系统允许您选择标准的打印功能,如份数、装订、颜色、纸张来源等。如果您需要选择特定于打印机的某个功能,则有一个“打印机属性”按钮,该按钮会调出所选打印机的设置对话框。
表单中有一个部分可以将打印机设置存储在“内存”中,以便以后使用。如果“内存”可以存储所有打印设置,包括那些无法从打印机设置类访问的设置,那不是很好吗?
您是什么意思“无法从打印机设置类访问”?
方向、分辨率、纸张来源、份数等设置存储在所谓的“Devmode 结构”中。该结构有两个部分
- 一个公共区域,包含方向、分辨率、纸张来源、份数等数据。这可以通过 .NET 中的打印机设置类进行访问。任何打印机都能够识别方向、分辨率、纸张来源、份数等的设置。
- 一个私有区域,包含特定于特定打印机设置的数据。纸张装订、折叠、最佳照片模式、喂狗、赶猫出门、提醒您结婚纪念日等。打印机设置类中没有处理这些的设置。
结构中的数据存储在连续的内存中,公共区域在前,紧接着是私有区域。如果我想存储私有区域中的数据,我将被卡住,因为没有定义私有区域中的数据(由于是打印机特定的,因此会因打印机而异)。我不想操作数据,只想存储和调用它。结构有一个公共部分的定义,您可以在此处找到。
我想存储所有已安装打印机的全部打印设置。问题是(抱歉,曾经是——让我们把时态弄对!)如何做到。
我找到了一种基于 Nicholas Piasecki 的想法的解决方案,他有一个“异想天开的想法”。请查看此链接,了解他是如何做到的。
将数据(devmode
结构)存储在文件中,然后在需要时重新加载文件,用文件中的 devmode
结构覆盖内存中的 devmode
结构,这就是我所采用的方法。
我添加了一些改进,但想法是相同的,因此功劳必须归于 Nicholas Piasecki,因为这是他的想法。
我认为最好的一点是,不需要 devmode
定义,因为我不需要知道变量名或值是什么,因为我不想更改它们。只需将数据副本作为“块”存储起来,而不是作为变量集合。原始版本确实使用了 devmode
定义,以便您可以找到 dm_Size
和 dm_Extra
变量的值。dm_Size
是 devmode
结构公共部分的大小(在我 Win 7 笔记本上,无论打印机如何,这都是 220 字节),dm_Extra
是 devmode
结构私有区域的大小,这因打印机而异。这两个值相加,以分配正确的内存量。这就是“所需大小”。如果您查看 printersettings
类,有一个名为“GetHdevmode
”的方法,它将返回 devmode
结构的全长!对于我的打印机,GetHdevmode
或 dm_Size
+ dm_Extra
之间没有区别,所以我放弃了定义,而是使用了 GetHdevmode
,这要容易得多。
Using the Code
主要有三种方法。
第一个方法打开打印机对话框(由打印机制造商提供的那个),以便您可以更改所选打印机的设置,包括打印机特定的设置。
这些方法需要以下平台调用才能工作
[DllImport("winspool.Drv",
EntryPoint = "DocumentPropertiesW",
SetLastError = true,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
static extern int DocumentProperties
(IntPtr hwnd, IntPtr hPrinter,
[MarshalAs(UnmanagedType.LPWStr)]
string pDeviceName,
IntPtr pDevModeOutput,
IntPtr pDevModeInput,
int fMode);
[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);
第一个方法调出打印机对话框
private PrinterSettings OpenPrinterPropertiesDialog(PrinterSettings printerSettings)
//Shows the printer settings dialogue that comes with the printer driver
{
IntPtr hDevMode = IntPtr.Zero;
IntPtr devModeData = IntPtr.Zero;
IntPtr hPrinter = IntPtr.Zero;
String pName = printerSettings.PrinterName;
try
{
hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
IntPtr pDevMode = GlobalLock(hDevMode);
int sizeNeeded = DocumentProperties(this.Handle, IntPtr.Zero, pName,
pDevMode, pDevMode, 0);//get needed size and allocate memory
if (sizeNeeded < 0)
{
MessageBox.Show("Bummer, Cant get size of devmode structure");
Marshal.FreeHGlobal(devModeData);
Marshal.FreeHGlobal(hDevMode);
devModeData = IntPtr.Zero;
hDevMode = IntPtr.Zero;
return printerSettings;
}
devModeData = Marshal.AllocHGlobal(sizeNeeded);
//show the native dialog
int returncode = DocumentProperties
(this.Handle, IntPtr.Zero, pName, devModeData, pDevMode, 14);
if (returncode < 0) //Failure to display native dialogue
{
MessageBox.Show("Dialogue Bummer, Got me a devmode, but the dialogue got stuck");
Marshal.FreeHGlobal(devModeData);
Marshal.FreeHGlobal(hDevMode);
devModeData = IntPtr.Zero;
hDevMode = IntPtr.Zero;
return printerSettings;
}
if (returncode ==2) //User clicked "Cancel"
{
GlobalUnlock(hDevMode);//unlocks the memory
if (hDevMode != IntPtr.Zero)
{
Marshal.FreeHGlobal(hDevMode); //Frees the memory
hDevMode = IntPtr.Zero;
}
if (devModeData != IntPtr.Zero)
{
GlobalFree(devModeData);
devModeData = IntPtr.Zero;
}
}
GlobalUnlock(hDevMode);//unlocks the memory
if (hDevMode != IntPtr.Zero)
{
Marshal.FreeHGlobal(hDevMode); //Frees the memory
hDevMode = IntPtr.Zero;
}
if (devModeData != IntPtr.Zero)
{
printerSettings.SetHdevmode(devModeData);
printerSettings.DefaultPageSettings.SetHdevmode(devModeData);
GlobalFree(devModeData);
devModeData = IntPtr.Zero;
}
}
catch (Exception ex)
{
MessageBox.Show("An error has occurred, caught and chucked back\n" + ex.Message);
}
finally
{
if (hDevMode != IntPtr.Zero)
{
Marshal.FreeHGlobal(hDevMode);
}
if (devModeData != IntPtr.Zero)
{
Marshal.FreeHGlobal(devModeData);
}
}
return printerSettings;
}
第二个方法获取 devmode
数据,并根据控制变量的值将其保存到文件或 ArrayList 中。此方法还使用与之前相同的平台调用。
private void GetDevmode(PrinterSettings printerSettings, int mode, String Filename)
//Grabs the devmode data in memory and stores in arraylist
{
///int mode
///1 = Save devmode structure to file
///2 = Save devmode structure to Byte array and arraylist
IntPtr hDevMode = IntPtr.Zero; // handle to the DEVMODE
IntPtr pDevMode = IntPtr.Zero; // pointer to the DEVMODE
IntPtr hwnd = this.Handle;
try
{
// 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);
int sizeNeeded = DocumentProperties
(hwnd, IntPtr.Zero, printerSettings.PrinterName, IntPtr.Zero, pDevMode, 0);
if (sizeNeeded <= 0)
{
MessageBox.Show("Devmode Bummer, Cant get size of devmode structure");
GlobalUnlock(hDevMode);
GlobalFree(hDevMode);
return;
}
DevModeArray = new byte[sizeNeeded]; //Copies the buffer into a byte array
if (mode == 1) //Save devmode structure to file
{
FileStream fs = new FileStream(Filename, FileMode.Create);
for (int i = 0; i < sizeNeeded; ++i)
{
fs.WriteByte(Marshal.ReadByte(pDevMode, i));
}
fs.Close();
fs.Dispose();
}
if (mode == 2) //Save devmode structure to Byte array and arraylist
{
for (int i = 0; i < sizeNeeded; ++i)
{
DevModeArray[i] = (byte)(Marshal.ReadByte(pDevMode, i));
//Copies the array to an arraylist where it can be recalled
}
}
// 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);
hDevMode = IntPtr.Zero;
}
catch (Exception ex)
{
if (hDevMode != IntPtr.Zero)
{
MessageBox.Show("BUGGER");
GlobalUnlock(hDevMode);
// And to boot, we don't need that DEVMODE anymore, either
GlobalFree(hDevMode);
hDevMode = IntPtr.Zero;
}
}
}
最后一个方法从硬盘上的文件或 ArrayList 中获取 devmode
数据(再次取决于控制变量),并覆盖内存中的结构。
private void SetDevmode(PrinterSettings printerSettings, int mode, String Filename)
//Grabs the data in arraylist and chucks it back into memory "Crank the suckers out"
{
///int mode
///1 = Load devmode structure from file
///2 = Load devmode structure from arraylist
IntPtr hDevMode = IntPtr.Zero; // a handle to our current DEVMODE
IntPtr pDevMode = IntPtr.Zero; // a pointer to our current DEVMODE
Byte[] Temparray;
try
{
DevModeArray = CurrentSetup.Devmodearray;
// 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);
// 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.
if (mode == 1) //Load devmode structure from file
{
FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read);
Temparray = new byte[fs.Length];
fs.Read(Temparray, 0, Temparray.Length);
fs.Close();
fs.Dispose();
for (int i = 0; i < Temparray.Length; ++i)
{
Marshal.WriteByte(pDevMode, i, Temparray[i]);
}
}
if (mode == 2) //Load devmode structure from arraylist
{
for (int i = 0; i < DevModeArray.Length; ++i)
{
Marshal.WriteByte(pDevMode, i, DevModeArray[i]);
}
}
// We're done futzing
GlobalUnlock(hDevMode);
// Tell our printer settings to use the one we just overwrote
printerSettings.SetHdevmode(hDevMode);
printerSettings.DefaultPageSettings.SetHdevmode(hDevMode);
// It's copied to our printer settings, so we can free the OS-level one
GlobalFree(hDevMode);
}
catch (Exception ex)
{
if (hDevMode != IntPtr.Zero)
{
MessageBox.Show("BUGGER");
GlobalUnlock(hDevMode);
// And to boot, we don't need that DEVMODE anymore, either
GlobalFree(hDevMode);
hDevMode = IntPtr.Zero;
}
}
}
其余方法处理 ArrayList、更新表单和比较器。
比较器允许显示两个 devmode
数据源(可以来自 ArrayList、DataFile 或两者),并查看它们之间的差异。结果显示在文本框中。
此解决方案并不完美,但对于我系统上安装的打印机来说,似乎确实有效。此解决方案并非用作独立应用程序,而是与具有打印功能的应用程序配合使用,但可以改编到您自己的应用程序中(假设您的应用程序需要驱动打印机)!
我已经在我的“Canon MP810”上尝试过,并能够存储和调用控制以下设置的设置:
- 打印前预览
- 纸张类型(普通纸、光面相纸、CD、T恤转印纸等
- 棕褐色调
- 图像优化器
- 照片优化 Pro 等
我也成功地在 PDF Creator(它将自己安装为打印机)上进行了尝试。
然而,“发送到 OneNote”有点冒险。如果您从 Send to One Note 对话框设置打印区域,保存,调用,然后再次查看对话框,打印区域有时会是零。我后来发现,如果从 One Note 驱动程序更改打印区域,纸张大小将变为“自定义”。当这个值传回 printersettings
类时,会返回默认的宽度和高度(零),而不是您输入的那个。我不得不以略微不同的方式处理自定义页面大小。
如果使用 ArrayList,还会存储其他数据,包括打印质量、纸张大小、打印机名称等。如果您选择不使用 devmode
结构(通过设置“否(鼠标)”),则使用这些设置。如果您选择“是(手动)”,则使用 devmode
结构,但如果从文件加载,请不要为选定的打印机加载错误的结构。
如果加载了错误的 devmode
结构,您的计算机可能会陷入不断缩小的循环,最终达到不可能!通过以下方式可以加载错误的 devmode
结构:
- 加载错误的二进制文件,对此没有检查。如果您获取一个电影文件,将其扩展名重命名为“.BIN”,该文件将出现在文件浏览器中。如果选择了这样的文件,您将覆盖
devmode
结构和大部分操作系统!如果使用 50 种灰度覆盖devmode
结构,您可能需要 GHOST 映像或恢复盘!!(不,这不会将打印机设置为黑白模式!)如果使用 ArrayList,则使用打印机名称首先调用正确的打印机,ArrayList 更安全。 - 更改操作系统和/或打印机驱动程序可能会导致问题,因为
devmode
结构可能会有所不同。别忘了,一个操作系统上的打印机驱动程序通常在另一个操作系统上不起作用。
我可能会研究一种方法,让其他应用程序知道打印机设置已更改,这样这个程序就可以作为独立程序来控制其他应用程序的打印机设置,但这留待以后,除非你们有人想试试?
正如我所说,此解决方案旨在以修改后的形式包含在另一个应用程序中。如果您看到任何可以改进解决方案的方法,请发布修改,以便任何人(包括我)都能从中受益。
许可证(这是什么用的)
我提供的任何内容我都视为“公共领域”。如果您想使用它,请下载项目然后继续。
可下载的 ZIP 文件是 VS2005 C# .NET 项目。