简体   繁体   English

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

[英]How to show printer properties/preferences dialog and save changes?

EDIT: My fault! 编辑:我的错! I expected the changes to be written back to the default printer settings when in fact only the local instance of the PrinterSettings are changed. 我希望在实际上只更改PrinterSettings的本地实例时,将更改写回默认的打印机设置。 - The below code seems to work as intended - 以下代码似乎按预期工作

I am trying to show the custom printer properties of a given printer. 我试图显示给定打印机的自定义打印机属性。 I need this as part of a custom PrintDialog which I am trying to write. 我需要这个作为我想写的自定义PrintDialog的一部分。

Most of the examples that I can find online manage to show the dialog but any changes the user might make are lost which makes it useless. 我在网上找到的大多数示例都设法显示对话框,但用户可能做出的任何更改都会丢失,这使得它无用。

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

(regarding above page: I tried to change the code as suggested by BartJoy (on the page) but that didn't fix it) (关于上面的页面:我试图改变BartJoy建议的代码(在页面上)但是没有修复它)

I also tried the sample and suggestions on the pinvoke.net page but it still doesn't work: 我也尝试过pinvoke.net页面上的示例和建议,但它仍然不起作用:

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

From the above websites I assume that the problem might only be on 64 bit Windows and/or if a printer name is longer than 32 characters. 从上面的网站我假设问题可能只在64位Windows上和/或如果打印机名称超过32个字符。

I don't know what I should try next... I appreciate any suggestions and comments! 我不知道接下来应该尝试什么...我感谢任何建议和意见!

EDIT: Here is what I have tried: 编辑:这是我尝试过的:

[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);
}

I have tried to use the OpenPrinter and ClosePrinter method and pass the devModeData as the output parameter in the second call as I found it strange that the original code from the pinvoke.net didn't do this. 我试图使用OpenPrinter和ClosePrinter方法并在第二次调用中将devModeData作为输出参数传递,因为我发现奇怪的是pinvoke.net的原始代码没有这样做。 (but I admit, that I don't know what I am doing - this is just trial and error). (但我承认,我不知道我在做什么 - 这只是反复试验)。

Here is the original code from the pinvoke site: 以下是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);
}

If you target x86 compilation and run from a x64 machine, the code from Jeff Roe will not work: when allocating devModeData , DocumentPropreties will always fail and returns a sizeNeeded of -1, with a LastError code 13. 如果你定位x86编译并从x64机器运行,那么来自Jeff Roe的代码将不起作用:在分配devModeDataDocumentPropreties将始终失败并返回sizeNeeded为-1,其LastError代码为13。

To solve the problem, either make sure you target AnyCPU or just change the call to DocumentPropreties to the following: 要解决此问题,请确保将目标设为AnyCPU,或者只是将对DocumentPropreties的调用更改为以下内容:

int sizeNeeded = DocumentProperties(pHandle, 
                                    IntPtr.Zero, 
                                    printerSettings.PrinterName, 
                                    IntPtr.Zero, // This solves it
                                    pDevMode, 
                                    fMode);

Using IntPtr.Zero instead of a proper pointer to a DevMode structure looks wrong, but that first call to DocumentProperties does not attempt to modify the memory at that position. 使用IntPtr.Zero而不是指向DevMode结构的正确指针看起来是错误的,但是对DocumentProperties的第一次调用不会尝试修改该位置的内存。 The only data returned by the call is the memory size needed to store the device mode data that represents the internal parameters of the print driver. 调用返回的唯一数据是存储表示打印驱动程序内部参数的设备模式数据所需的内存大小。

Reference: 参考:

Even though the answer ended up working its way into the question, I think the following provides a better answer to the original question, 即使答案最终解决了问题,我认为以下内容为原始问题提供了更好的答案,

(1) Because it clearly does not modify the passed-in PrinterSettings if the user cancels. (1)因为如果用户取消,它显然不会修改传入的PrinterSettings。

(2) Because it returns a DialogResult, which the caller will likely be interested in. (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;
}
  • when your application started up: 当您的应用程序启动时:
    • have you queried the printer driver for the correct size of the DEVMODE structure before allocating it? 你有没有在分配它之前查询打印机驱动程序的DEVMODE结构的正确大小?
    • have you asked the device driver to initialize the DEVMODE buffer with the default settings after you have allocated it? 你有没有要求设备驱动程序在分配后用默认设置初始化DEVMODE缓冲区?
  • when your application popped up the printer dialog: 当您的应用程序弹出打印机对话框时:
    • have you set the DM_IN_BUFFER and DM_OUT_BUFFER flags (in addition to DM_IN_PROMPT ) in the fMode parameter to DocumentProperties ? 你有没有将fMode参数中的DM_IN_BUFFERDM_OUT_BUFFER标志(除了DM_IN_PROMPTfModeDocumentProperties
    • have you pointed both pDevModeInput and pDevModeOutput to the DEVMODE buffer you initialized at application startup? 你有没有将pDevModeInputpDevModeOutput指向你在应用程序启动时初始化的DEVMODE缓冲区?
    • are the dmFields bits in the DEVMODE buffer properly set prior to your calling DocumentProperties(... DM_IN_PROMPT ...) 是在调用DocumentProperties(... DM_IN_PROMPT ...)之前正确设置的DEVMODE缓冲区中的dmFields
    • are you preserving the contents of the DEVMODE buffer in between calls to DocumentProperties(... DM_IN_PROMPT ...) ? 你是否在对DocumentProperties(... DM_IN_PROMPT ...)调用之间保留了DEVMODE缓冲区的内容?

See: 看到:

Also, if you want to do this using WPF classes (PrintQueue, PrintTicket) this page points you to the right direction: 此外,如果您想使用WPF类(PrintQueue,PrintTicket)执行此操作,此页面将指向正确的方向:

http://social.msdn.microsoft.com/Forums/en/wpf/thread/0dc695c1-578d-4da5-8f68-b2a257846c02 http://social.msdn.microsoft.com/Forums/en/wpf/thread/0dc695c1-578d-4da5-8f68-b2a257846c02

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM