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. -
DEVMODE
structure before allocating it?DEVMODE
buffer with the default settings after you have allocated it?DM_IN_BUFFER
and DM_OUT_BUFFER
flags (in addition to DM_IN_PROMPT
) in the fMode
parameter to DocumentProperties
?pDevModeInput
and pDevModeOutput
to the DEVMODE
buffer you initialized at application startup?dmFields
bits in the DEVMODE
buffer properly set prior to your calling DocumentProperties(... DM_IN_PROMPT ...)
DEVMODE
buffer in between calls to DocumentProperties(... DM_IN_PROMPT ...)
?See:
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.
(2) Because it returns a DialogResult, which the caller will likely be interested in.
[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;
}
Also, if you want to do this using WPF classes (PrintQueue, PrintTicket) this page points you to the right direction:
http://social.msdn.microsoft.com/Forums/en/wpf/thread/0dc695c1-578d-4da5-8f68-b2a257846c02
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.
To solve the problem, either make sure you target AnyCPU or just change the call to DocumentPropreties
to the following:
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. 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: