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 a DEVMODE
structure. Must be set to the current print document PrintSettings.GetHdevmode()
member to make our customize dialog display our current printer settings.
hDevNames
- Handle to a movable global memory object that contains a DEVNAMES
structure. Must be set to the current print document PrintSettings.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, the PD_ENABLEPRINTHOOK
flag must be set in order to make our print dialog hook procedure implementation to be used. See lpfnPrintHook
below.
lpfnPrintHook
- a function pointer, set to our print dialog hook procedure(PrintHookProcImpl
). The PD_ENABLEPRINTHOOK
flag must be added to the Flags
member to make this setting effective.
PRINTDLG _pd = new PRINTDLG();
_pd.lStructSize = Marshal.SizeOf(_pd);
_pd.hwndOwner = this.Parent;
_pd.hDevMode = printerSettings.GetHdevmode();
_pd.hDevNames = printerSettings.GetHdevnames();
_pd.Flags = PrintFlag.PD_ENABLEPRINTHOOK | PrintFlag.PD_NOPAGENUMS;
_pd.lpfnPrintHook = new PrintHookProc( PrintHookProcImpl );
bool bRet = PrintDlg(_pd);
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 )
{
switch(msg)
{
case WndMsg.WM_INITDIALOG:
{
this.Handle = hPrintDlgWnd;
UInt32 nZOrder = (this.TopMost) ? WndZOrder.HWND_TOPMOST :
WndZOrder.HWND_TOP;
Win32.SetWindowPos(
this.Handle,
nZOrder,
0, 0, 0, 0,
WndPos.SWP_NOSIZE |
WndPos.SWP_NOMOVE
);
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();
oPrintDlg.Parent = this.Handle;
oPrintDlg.TopMost = true;
oPrintDlg.HandlePrintEvent += new EventHandler(OnPrintEvent);
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.
A graduate in Bachelor of Science in Computer Engineering from the Philippines. His love for software programming/design/patterns started in his college days when he found out that he possesses above average skill in computer programming. He started his career as a computer instructor for a year, then shifts to work as a software design engineer in one of the renowned Japanese company in the Philippines. After almost 5 years working in that company he got an opportunity to work in japan were he now currently lives and work since 2003.