如何显示打印机属性/首选项对话框并保存更改?

8

编辑:我的错!我原本期望更改会写回默认的打印机设置,但实际上只有打印机设置的本地实例被更改了。 - 下面的代码似乎按照预期工作

我想显示给定打印机的自定义打印机属性。我需要这个作为我试图编写的自定义PrintDialog的一部分。

我能在网上找到的大多数示例都可以显示对话框,但用户可能做出的任何更改都会丢失,这使得它无用。

例如: http://www.codeproject.com/KB/system/PrinterPropertiesWindow.aspx

(关于上面的页面:我尝试根据页面上的BartJoy的建议更改代码,但那并没有修复它)

我还尝试了pinvoke.net页面上的示例和建议,但仍然无法工作:

http://www.pinvoke.net/default.aspx/winspool.documentproperties

从上述网站中,我认为问题可能只存在于64位Windows和/或打印机名称超过32个字符时。

我不知道下一步该尝试什么... 我感激任何建议和评论!

编辑:这是我尝试过的:

[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("winspool.drv")]
private static extern int OpenPrinter(string pPrinterName, out IntPtr hPrinter, IntPtr pDefault);
[DllImport("winspool.drv")]
private static extern int ClosePrinter(IntPtr phPrinter);

[DllImport("kernel32.dll")]
static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll")]
static extern bool GlobalFree(IntPtr hMem);

private const int DM_PROMPT = 4;
private const int DM_OUT_BUFFER = 2;
private const int DM_IN_BUFFER = 8;

private void OpenPrinterPropertiesDialog()
{
    var printerSettings = new System.Drawing.Printing.PrinterSettings();
    var printerName = printerSettings.PrinterName;

    IntPtr handle;
    OpenPrinter(printerName, out handle, IntPtr.Zero);

    IntPtr hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
    IntPtr pDevMode = GlobalLock(hDevMode);
    int sizeNeeded = DocumentProperties(this.Handle, handle, printerName, pDevMode, pDevMode, 0);
    IntPtr devModeData = Marshal.AllocHGlobal(sizeNeeded);
    DocumentProperties(this.Handle, handle, printerName, devModeData, pDevMode, DM_IN_BUFFER | DM_PROMPT | DM_OUT_BUFFER);

    ClosePrinter(handle);
    GlobalUnlock(hDevMode);

    printerSettings.SetHdevmode(devModeData);
    printerSettings.DefaultPageSettings.SetHdevmode(devModeData);

    GlobalFree(hDevMode);
    Marshal.FreeHGlobal(devModeData);
}

我尝试使用OpenPrinter和ClosePrinter方法,并将devModeData作为第二次调用的输出参数传递,因为我发现pinvoke.net上的原始代码没有这样做(但我承认,我不知道我在做什么-这只是试错)。
以下是来自pinvoke网站的原始代码:
private void OpenPrinterPropertiesDialog(PrinterSettings printerSettings)
{
    IntPtr hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
    IntPtr pDevMode = GlobalLock(hDevMode);
    int sizeNeeded = DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, pDevMode, pDevMode, 0);
    IntPtr devModeData = Marshal.AllocHGlobal(sizeNeeded);
    DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, IntPtr.Zero, pDevMode, 14);
    GlobalUnlock(hDevMode);
    printerSettings.SetHdevmode(devModeData);
    printerSettings.DefaultPageSettings.SetHdevmode(devModeData);
    GlobalFree(hDevMode);
    Marshal.FreeHGlobal(devModeData);
}

你是如何将更改保存回去的呢?这段代码确实会更改打印机设置,但更改并没有保存为默认打印机设置 :( - Ando
4个回答

7
尽管答案最终被融入了问题中,但我认为以下内容提供了更好的原始问题答案:
(1)因为如果用户取消操作,它明确不会修改传入的PrinterSettings。
(2)因为它返回一个DialogResult,调用者可能会感兴趣。
[DllImport("kernel32.dll")]
static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll")]
static extern bool GlobalFree(IntPtr hMem);
[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);

private const int DM_PROMPT = 4;
private const int DM_OUT_BUFFER = 2;
private const int DM_IN_BUFFER = 8;

private DialogResult EditPrinterSettings(PrinterSettings printerSettings)
{
    DialogResult myReturnValue = DialogResult.Cancel;
    IntPtr hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
    IntPtr pDevMode = GlobalLock(hDevMode);
    int sizeNeeded = DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, pDevMode, pDevMode, 0);
    IntPtr devModeData = Marshal.AllocHGlobal(sizeNeeded);
    long userChoice = DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, devModeData, pDevMode, DM_IN_BUFFER | DM_PROMPT | DM_OUT_BUFFER);
    long IDOK = (long)DialogResult.OK;
    if (userChoice == IDOK)
    {
        myReturnValue = DialogResult.OK;
        printerSettings.SetHdevmode(devModeData);
        printerSettings.DefaultPageSettings.SetHdevmode(devModeData);
    }
    GlobalUnlock(hDevMode);
    GlobalFree(hDevMode);
    Marshal.FreeHGlobal(devModeData);
    return myReturnValue;
}

@JeffRow 我刚刚在一台Win8 64位机器上尝试了您提供的代码,DocumentProperties返回-1,因此当调用Marshal.AllocHGlobal(sizeNeeded)时,它会抛出错误“内存不足以继续执行程序。”这是有道理的,因为sizeNeeded为-1。 - Thierry
1
虽然还处于早期阶段,但这似乎是我迄今为止找到的最稳定的代码,以解决我的问题。唯一的小变化是根据我的先前评论,在获取sizeNeeded时,需要更改API调用,使用IntPtr.Zero而不是0,即Dim sizeNeeded As Integer = DocumentProperties(Me.Handle, IntPtr.Zero, printerSettings.PrinterName, IntPtr.Zero, pDevMode, 0)。感谢分享! - Thierry

7
如果你的目标是x86编译并在x64机器上运行,Jeff Roe的代码将无法工作:当分配devModeData时,DocumentPropreties将总是失败并返回一个sizeNeeded为-1,带有一个LastError代码13。
为了解决这个问题,要么确保你的目标是AnyCPU,要么只需更改对DocumentPropreties的调用如下:
int sizeNeeded = DocumentProperties(pHandle, 
                                    IntPtr.Zero, 
                                    printerSettings.PrinterName, 
                                    IntPtr.Zero, // This solves it
                                    pDevMode, 
                                    fMode);

使用 IntPtr.Zero 而不是正确的指向 DevMode 结构体的指针看起来是错误的,但是第一次调用 DocumentProperties 函数并未尝试修改该位置的内存。此调用仅返回表示打印驱动程序内部参数的设备模式数据所需的内存大小。

参考:


3
  • 当您的应用程序启动时:
    • 您是否在分配DEVMODE结构之前查询打印机驱动程序以获取正确的大小?
    • 您是否在分配DEVMODE结构后请求设备驱动程序使用默认设置初始化DEVMODE缓冲区?
  • 当您的应用程序弹出打印机对话框时:
    • 您是否在fMode参数中设置了DM_IN_BUFFERDM_OUT_BUFFER标志(除了DM_IN_PROMPT)以调用DocumentProperties函数?
    • 您是否将pDevModeInputpDevModeOutput都指向应用程序启动时初始化的DEVMODE缓冲区?
    • 在调用DocumentProperties(... DM_IN_PROMPT ...)之前,DEVMODE缓冲区中的dmFields位是否正确设置?
    • 您是否在调用DocumentProperties(... DM_IN_PROMPT ...)之间保留DEVMODE缓冲区的内容?

请参见:


感谢您的输入。我相信我已经做了这些事情。我已经更新了问题并包含了我尝试过的代码。 - Patrick Klug
我的错!我期望将更改写回默认打印机设置,这样当我用新的PrinterSettings()调用相同的方法时,它会反映过去的更改。-- 看起来一切都正常工作,因为打印机设置已经正确更新了。 - Patrick Klug

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接