Extending C# PrintDialog - Part I






4.52/5 (9 votes)
Dec 17, 2006
3 min read

103422

3517
An extended C# PrintDialog with topmost capability.
Introduction
C# provides a standard PrintDialog
which has all the basic print dialog functionality. Since it is declared as a sealed
class, there is no way to extend it through C# native syntax. This article provides an alternate means to extend the functionality of the C# PrintDialog
.
There are a lot of additional features that can be added to the common print dialog, one's imagination is only the limit. In this article, I will present an extended print dialog with a topmost and persistent location window capability.
Overview
PrintDialogEx
serves as a wrapper to the system PrintDlg (common dialog), which uses P/Invoke methods to access the Win32 system's low-level APIs. Hook the dialog window mainproc, and add a topmost window style when it receives a WM_INIT_DIALOG
event notification. This may sound complicated, but it's all under the hood.
Under the Hood
The Win32 PrintDlg(...)
API exported by comdlg32.dll provides a way to customize the system's standard print dialog. Since, this API is only available in Win32, the trick is to use P/Invoke to map this API to make it available in C#.
[DllImport("comdlg32.dll", CharSet=CharSet.Auto)] static
extern bool PrintDlg([In,Out] PRINTDLG lppd);
The parameter to the API is a pointer to PRINTDLG
. Below is the PRINTDLG
structure from commdlg.h, ported to C#:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto, Pack = 1)]
[System.Runtime.InteropServices.ComVisible(false)]
internal class PRINTDLG
{
public Int32 lStructSize;
public IntPtr hwndOwner;
public IntPtr hDevMode;
public IntPtr hDevNames;
public IntPtr hDC = IntPtr.Zero;
public Int32 Flags;
public Int16 FromPage = 0;
public Int16 ToPage = 0;
public Int16 MinPage = 0;
public Int16 MaxPage = 0;
public Int16 Copies = 0;
public IntPtr hInstance = IntPtr.Zero;
public IntPtr lCustData = IntPtr.Zero;
public PrintHookProc lpfnPrintHook;
public IntPtr lpfnSetupHook = IntPtr.Zero;
public IntPtr lpPrintTemplateName = IntPtr.Zero;
public IntPtr lpSetupTemplateName = IntPtr.Zero;
public IntPtr hPrintTemplate = IntPtr.Zero;
public IntPtr hSetupTemplate = IntPtr.Zero;
}
I will discuss only the important structure members necessary to achieve the goal of this article. For a detailed description of this structure, please refer to the PRINTDLG MSDN documentation.
hDevMode
- Handle to a movable global memory object that contains aDEVMODE
structure. Must be set to the current print documentPrintSettings.GetHdevmode()
member to make our customize dialog display our current printer settings.hDevNames
- Handle to a movable global memory object that contains aDEVNAMES
structure. Must be set to the current print documentPrintSettings.GetHdevmode()
member to make our customize dialog display our current printer settings.Flags
- A set of bit flags that you can use to initialize the print dialog box. Setting the value of this flag enables us to enable/disable/show/hide some controls in the standard print dialog. But most importantly, thePD_ENABLEPRINTHOOK
flag must be set in order to make our print dialog hook procedure implementation to be used. SeelpfnPrintHook
below.lpfnPrintHook
- a function pointer, set to our print dialog hook procedure(PrintHookProcImpl
). ThePD_ENABLEPRINTHOOK
flag must be added to theFlags
member to make this setting effective.
// PRINTDLG that will be passed to Comm32.dll PrintDlg API function.
PRINTDLG _pd = new PRINTDLG();
_pd.lStructSize = Marshal.SizeOf(_pd);
// handle to parent form instance
_pd.hwndOwner = this.Parent;
// Pass the handles to the DEVMODE and DEVNAMES structures from the
// printer settings object to the PRINTDLG so that the current
// printsettings are displayed in the print setup dialog.
_pd.hDevMode = printerSettings.GetHdevmode();
_pd.hDevNames = printerSettings.GetHdevnames();
// indicates that only the print dialog mainproc will be hooked.
_pd.Flags = PrintFlag.PD_ENABLEPRINTHOOK | PrintFlag.PD_NOPAGENUMS;
// set the print dialog hook implementation to be used.
_pd.lpfnPrintHook = new PrintHookProc( PrintHookProcImpl );
// actual displaying of the dialog
bool bRet = PrintDlg(_pd);
// Pass the resulting DEVMODE and DEVNAMES structs back to the
// caller via the PrinterSettings object that was passed in.
printerSettings.SetHdevmode(_pd.hDevMode);
printerSettings.SetHdevnames(_pd.hDevNames);
Notice that lpfnPrintHook
was set to an intance of PrintHookProc
with the PrintHookProcImpl
function as parameter. PrintHookProc
is a delegate function which is equivalent to a function pointer in C++. You can create an instance of PrintHookProc
with an argument equal to any method as long as the parameters match the PrintHookProc
declaration. Below is the declaration:
internal delegate IntPtr PrintHookProc( IntPtr hWnd, UInt16 msg,
Int32 wParam, Int32 lParam );
Once, the PRINTDLG
structure instance is properly intialized, the only next thing to do is to invoke our mapped Win32 PrintDlg()
, passing our PRINTDLG
as the argument. The code snippet above will display the system's standard print dialog, but prior to displaying will raise a WM_INIT_DIALOG
event, which our hook procedure intercepts.
internal IntPtr PrintHookProcImpl( IntPtr hPrintDlgWnd,
UInt16 msg, Int32 wParam, Int32 lParam )
{
// Evaluates the message parameter to determine
// windows event notifications.
switch(msg)
{
case WndMsg.WM_INITDIALOG:
{
// save the handle to print dialog window for future use.
this.Handle = hPrintDlgWnd;
// window z-order position
UInt32 nZOrder = (this.TopMost) ? WndZOrder.HWND_TOPMOST :
WndZOrder.HWND_TOP;
// set this dialog z-order to topmost.
Win32.SetWindowPos(
this.Handle, // handle to window
nZOrder, // placement-order handle
0, 0, 0, 0, // dont-cares since,
// this window options is set to
// SWP_NOSIZE
WndPos.SWP_NOSIZE |
WndPos.SWP_NOMOVE // window-positioning options
);
break;
}
}
return IntPtr.Zero;
}
Using the code
The client application needs only to set the PrintDialogEx
's TopMost
property to true
prior to calling ShowDialog()
method.
Below is a sample code snippet to do this:
PrintDialogEx oPrintDlg = new PrintDialogEx();
// set the dialogs owner window(optional)
// parent handle should be a handle to a window Form
oPrintDlg.Parent = this.Handle;
// set this dialog as topmost window.
oPrintDlg.TopMost = true;
// set the print button event handler(optional)
// event handler method will be called when user
// clicks the OK button from PrintDialogEx window.
// if this property is not set, built-in print handler will be called.
oPrintDlg.HandlePrintEvent += new EventHandler(OnPrintEvent);
// associate this dialog to a print document instance.
oPrintDlg.Document = new System.Drawing.Printing.PrintDocument();
DialogResult nResult = oPrintDlg.ShowDialog();
References
- Printing Overview, MSDN documentation.
- Extend the Common Dialog Boxes, MSDN tutorials.
History
- 2006.11.25 - Original version.