Click here to Skip to main content
Click here to Skip to main content

Extending C# PrintDialog - Part I

, 17 Dec 2006
Rate this:
Please Sign up or sign in to vote.
An extended C# PrintDialog with topmost capability.

Sample Image - TopMostPrintDlg.gif

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. <!-- What the article/code snippet does, why it's useful, the problem it solves etc. -->

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 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

  1. Printing Overview, MSDN documentation.
  2. Extend the Common Dialog Boxes, MSDN tutorials.

History

  • 2006.11.25 - Original version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Christopher Tan
Web Developer
Singapore Singapore
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.

Comments and Discussions

 
QuestionPrintDialogEx ShowDIalog prints a blank page PinmemberKeith0126-May-11 5:22 
GeneralMy vote of 5 Pinmemberhc2708040128-Apr-11 6:46 
Thank you very much!Your article help me a lot!
GeneralPrint Setup Dialog Pinmembervinaysharma773-Jan-11 11:57 
GeneralPrintDialog Control Pinmembervarundj18-Sep-08 22:25 
QuestionPrint Preview issue : PinmemberCoolCoder_New24-Jun-08 5:29 
QuestionProperties PinmemberTDWiseley23-Feb-07 6:35 
GeneralPart2 Pinmemberdiobertd17-Dec-06 17:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 17 Dec 2006
Article Copyright 2006 by Christopher Tan
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid