DocumentProperties (winspool)
Last changed: Anisan-179.178.48.53

.
Summary
Brings up a printer properties page

C# Signature:

[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", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int DocumentProperties(IntPtr hWnd, IntPtr hPrinter, string pDeviceName, IntPtr pDevModeOutput, IntPtr pDevModeInput, fModes fMode);

VB Signature:

  <DllImport("winspool.Drv", EntryPoint:="DocumentPropertiesW", SetLastError:=True, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Private Shared Function DocumentProperties(ByVal hwnd As IntPtr, ByVal hPrinter As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal pDeviceName As String, ByVal pDevModeOutput As IntPtr, ByVal pDevModeInput As IntPtr, ByVal fMode As Integer) As Integer
    End Function

User-Defined Types:

None.

Alternative Managed API:

Do you know one? Please contribute it!

Notes:

For the values for the fMode parameter have a look at the fModes enum.

Notes for x64 sytems

Doesn't work on a x64 system. (i.e. this declaration is wrong for x64, I'm just not quite sure why.). MH:I tested the code extensivly and I think the param "IntPtr pDevModeInput" must not have the "ref" flag. If it has the initial state of the dialog is wrong. EP: Yup, you're right! Thanks!!! I've been trying to figure out why it doesn't work on some drivers for a few weeks now! "ref" removed.

- - - - - - - - -

I had similar issues under x64 so i invstigated a bit and found the solution!

"DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, pDevMode, pDevMode, 0);" returns -1 and error code 183 under x64 however i dnt knw y!

in order to get it working use "DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, IntPtr.Zero, pDevMode, 0);"

Tips & Tricks:

Please add some!

Sample Code:

Obtain a pointer to the DEVMODE struct (either printerHandle or printerName can be NULL | IntPtr.ZERO):

public IntPtr GetDevMode(IntPtr printerHandle, string printerName)
{
   int sizeNeeded = DocumentProperties(IntPtr.Zero, printerHandle, printerName, IntPtr.Zero, IntPtr.Zero, fModes.DM_SIZEOF);
   if (sizeNeeded < 0)
   {
     throw new Win32Exception(Marshal.GetLastWin32Error());
   }

   // not really required, but see example: http://support.microsoft.com/kb/828638/en-us
   sizeNeeded += 100;

   IntPtr pdevmode = Marshal.AllocHGlobal(sizeNeeded);

   int result = DocumentProperties(IntPtr.Zero, printerHandle, printerName, pdevmode, IntPtr.Zero, fModes.DM_OUT_BUFFER);
   if (result < 0)
   {
     throw new Win32Exception(Marshal.GetLastWin32Error());
   }

   return pdevmode;
}

Don't forget to free the memory with

Marshal.FreeHGlobal(IntPtr);

    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, devModeData, pDevMode, 14);
        GlobalUnlock(hDevMode);
        printerSettings.SetHdevmode(devModeData);
        printerSettings.DefaultPageSettings.SetHdevmode(devModeData);
        GlobalFree(hDevMode);
        Marshal.FreeHGlobal(devModeData);
    }

Documentation

Sample works under both x64 and x86:

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

Matthias' Comment:

In my case, the following code snippet did the trick for both x64 and x86:

I had to change the call to DocumentProperties: The third-from-last parameter MUST be IntPtr.Zero.

(this was mentioned above by someone else, I take no credit for it )

    private void OpenPrinterPropertiesDialog(PrinterSettings printerSettings)
    {
        IntPtr hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
        IntPtr pDevMode = GlobalLock(hDevMode);
        int sizeNeeded = DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, IntPtr.Zero, 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);
    }

Radoslav' Comment:

I had to fixed Matthias solution, because it doesn't work well for me. Exception is no more occurs, but when some setting is changed in printer properties it isn't used by printer. I've changed second call of DocumentProperties method. The third-from-last paramter MUST be in this case devModeData.

    private void OpenPrinterPropertiesDialog(PrinterSettings printerSettings)
    {
        IntPtr hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
        IntPtr pDevMode = GlobalLock(hDevMode);
        int sizeNeeded = DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, IntPtr.Zero, pDevMode, 0);
        IntPtr devModeData = Marshal.AllocHGlobal(sizeNeeded);
        DocumentProperties(this.Handle, IntPtr.Zero, printerSettings.PrinterName, devModeData, pDevMode, 14);
        GlobalUnlock(hDevMode);
        printerSettings.SetHdevmode(devModeData);
        printerSettings.DefaultPageSettings.SetHdevmode(devModeData);
        GlobalFree(hDevMode);
        Marshal.FreeHGlobal(devModeData);
    }

I believe the issue(s) discussed re:x64 is actually an odd idiosyncracy under Windows Server 2008 R2 x64, but is not a x86/x64 problem per se. At least testing this under Win7 x64, I have no issues. However, under W2K8 x64, the OutBuffer and InBuffer must NOT be the same (in Win7 x64 this works OK). ALSO, the call to set doc properties with fMode = 14 (Prompt | In | Out) and OutBuffer = IntPtr.Zero CANNOT possibly correct, as in this case there is no place to put the resulting Devmode. Good code shouldn't use "magic numbers" anyway. The following values from WinSpool.h should be used instead of "14" or "0" for the last parameter in this call:

       /* mode selections for the device mode function */
    public const uint DM_UPDATE = 1;
    public const uint DM_COPY = 2;
    public const uint DM_PROMPT = 4;
    public const uint DM_MODIFY = 8;

    public const uint DM_IN_BUFFER = DM_MODIFY;
    public const uint DM_IN_PROMPT = DM_PROMPT;
    public const uint DM_OUT_BUFFER = DM_COPY;
    public const uint DM_OUT_DEFAULT = DM_UPDATE;

EDIT 13.09.2013: For the values for the fMode parameter have a look at the fModes enum.

Edit 10APR2013:

On a Win8 x64 system, I encountered the issue when I was trying to target x86 compiling. The call to DocumentProperties would fail and return -1 for sizeNeeded.

If I target x64 or AnyCPU, everything is working fine. However, because of other constraints, I had to target x86 and couldn't make it work without using the IntPtr.Zero fix. Now it works in every configuration.

Agreed that the use of a IntPrt.Zero where a recipient data structure is expected looks wrong, but it should be ok in this case: that first call to DocumentProperties does not attempt to save any data to the DevMode structure, it just returns the size of memory necessary to hold all the devModeData held by the printer driver, nothing else.