Click to search the site Click to log in
Online articles
Download free tools
Support pages, per product
Services
Frequently asked questions, per product
Windows Forms Custom Print Dialog
Author: George Mihaescu
Published: August 21, 2007
Category: Implementation technique / Windows Forms .Net (all versions)
Notes:
Description: This article presents my solution for customizing the print dialog in a .Net Windows Forms application using P/Invoke. Tested with .Net runtime 1.0, 1.1 and 2.0 on Win 2000, XP and Vista. A .zip file is available for download with source code and the compiled binaries (.Net 2.0).
View count: 2,470
Comments: 3 Read comments or post your own

  Print viewOpens in new window
 Windows Forms - Custom Print Dialog

.Net Windows Forms: How to Create a Custom Print Dialog

 

By George Mihaescu

 

Summary: this article presents a solution for customizing the print dialog in a .Net Windows Forms application. The solution presented relies on P/Invoke (direct call into the Win32 API from .Net) and has been tested with .Net runtime 1.0, 1.1 and 2.0 on Win 2000, XP and Vista.

A .zip file is available for download with source code and the compiled binaries (.Net 2.0).

The problem

Your .Net Windows Forms application needs additional controls in the standard print dialog. The example in the associated source code adds a check box which allows the user the additional functionality to "shrink the output to fit on a single page". However, the object-oriented solution that immediately comes to mind (inherit from System.Windows.Forms.PrintDialog and extend its functionality) cannot be applied because the PrintDialog class is sealed. (As a side, it is sealed because PrintDialog is just a thin wrapper over the standard Win32 Print Common Dialog, and simply deriving from it would not have worked – although with additional programming effort Microsoft could have provided set of methods to allow customization of the dialog through inheritance, something similar to what I'm doing in the sample code attached: see CustomPrintDialog.cs).

The solution

Derive your own class from System.Windows.Forms.CommonDialog and use P/Invoke to call into the Win32 API PrintDlg. This function takes a structure of type PRINTDLG, which has a field that allow a pointer to a callback function specifically for customization. By creating a method in your .Net code that matches the signature of the expected callback and specifying a delegate wrapping it in the member of the structure, your method will be called upon the standard Print Dialog initialization – from this point on you are free to customize the dialog as you see fit (hide controls, add controls, etc).

 

This is how the customized dialog will look like using the code in my example (note the additional checkbox at the bottom of the dialog, which I dynamically extend vertically in my callback implementation in order to make room for the new checkbox):

 

 

 

The core of the code is show below, but I strongly suggest you download the full sample.

 

#region Imports from the Win32 API

 

private const int PD_ALLPAGES = 0x00000000;

private const int PD_NOSELECTION = 0x00000004;

private const int PD_NOPAGENUMS = 0x00000008;

private const int PD_ENABLEPRINTHOOK = 0x00001000;

 

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Auto)]

private struct PRINTDLG

{

    public Int32 lStructSize;

    public IntPtr hwndOwner;

    public IntPtr hDevMode;

    public IntPtr hDevNames;

    public IntPtr hDC;

    public Int32 Flags;

    public UInt16 nFromPage;

    public UInt16 nToPage;

    public UInt16 nMinPage;

    public UInt16 nMaxPage;

    public UInt16 nCopies;

    public IntPtr hInstance;

    public Int32 lCustData;

    public PrintHookProc lpfnPrintHook;

    public IntPtr lpfnSetupHook;

    public string lpPrintTemplateName;

    public string lpSetupTemplateName;

    public IntPtr hPrintTemplate;

    public IntPtr hSetupTemplate;

}

 

[DllImport("comdlg32.dll", CharSet = CharSet.Auto)]

private static extern bool PrintDlg(ref PRINTDLG pdlg);

 

 

private const int WM_INITDIALOG = 0x0110;

 

private const int IDOK = 1;

private const int IDCANCEL = 2;

 

#endregion

 

#region Customization of the standard dialog through a callback (print hook procedure)

 

private delegate IntPtr PrintHookProc(IntPtr hdlg, Int32 msg, UInt16 wparam, Int32 lparam);

 

 

/// <summary>

/// Signature matching the delegate above which we instantiate and pass to

/// the print dialog as the customization method.

/// </summary>

private IntPtr PrintCustomizationMethod(IntPtr hdlg, Int32 msg, UInt16 wparam, Int32 lparam)

{

    if (msg == WM_INITDIALOG)

    {

        //create checkbox and attach it to the dialog

        SetParent(m_cb.Handle, hdlg);

 

        //get current dialog size

        RECT rect = new RECT();

        GetWindowRect(hdlg, ref rect);

 

        //place the checkbox at the bottom of the dialog (using the top of the OK button as reference)

        RECT button_rect = new RECT();

        IntPtr h_button = GetDlgItem(hdlg, IDOK);

        GetWindowRect(h_button, ref button_rect);

 

        POINT pt;

        pt.x = button_rect.left;

        pt.y = button_rect.top;

        ScreenToClient(hdlg, ref pt);

 

        m_cb.Location = new Point(12, pt.y);

        m_cb.Size = new Size(rect.Width - 12, m_cb.Size.Height);

        m_cb.Visible = true;

        m_cb.Text = "Compress output to fit in one page";

 

        //set initial state and attach handler to combo box check event

        m_cb.Checked = this.ShrinkOutputToFitOnOnePage;

        m_cb.CheckedChanged += new EventHandler(cb_CheckedChanged);

 

        //extend the dialog down with the height of the checkbox

        SetWindowPos(hdlg, new IntPtr(0), 0, 0, rect.Width, rect.Height + m_cb.Bounds.Height,

                      SWP_NOZORDER | SWP_NOMOVE);

 

        //move the OK and Cancel buttons down

        h_button = GetDlgItem(hdlg, IDOK);

        GetWindowRect(h_button, ref rect);

        pt.x = rect.left;

        pt.y = rect.top;

        ScreenToClient(hdlg, ref pt);

 

        SetWindowPos(h_button, new IntPtr(0), pt.x, pt.y + m_cb.Bounds.Height, 0, 0,

                      SWP_NOZORDER | SWP_NOSIZE);

 

        h_button = GetDlgItem(hdlg, IDCANCEL);

        GetWindowRect(h_button, ref rect);

        pt.x = rect.left;

        pt.y = rect.top;

        ScreenToClient(hdlg, ref pt);

 

        SetWindowPos(h_button, new IntPtr(0), pt.x, pt.y + m_cb.Bounds.Height, 0, 0,

                      SWP_NOZORDER | SWP_NOSIZE);

    }

 

    //allow processing of messages by default procedure by returning 0

    return new IntPtr(0);

}

 

/// <summary>

/// Event handler for the custom combo box check event

/// </summary>

private void cb_CheckedChanged(object sender, EventArgs e)

{

    CheckBox cb = sender as CheckBox;

    this.ShrinkOutputToFitOnOnePage = cb.Checked;

}

 

#endregion

 

#region Implementation of abstract methods of the base

 

protected override bool RunDialog(System.IntPtr hwndOwner)

{

    //initialize the structure required by the PrintDlg Win32 API

    PRINTDLG pdlg = new PRINTDLG();

    pdlg.lStructSize = Marshal.SizeOf(pdlg);

    pdlg.hwndOwner = hwndOwner;

 

    pdlg.Flags |= PD_ALLPAGES;

    if (!this.AllowSelection)

    {

        pdlg.Flags |= PD_NOSELECTION;

    }

    if (!this.AllowSomePages)

    {

        pdlg.Flags |= PD_NOPAGENUMS;

    }

 

    //set the hook procedure to point to our instance of the delegate designed

    //for customizing the dialog. Without this, the class will just show the standard print dialog.

    pdlg.Flags |= PD_ENABLEPRINTHOOK;

    pdlg.lpfnPrintHook = new PrintHookProc(this.PrintCustomizationMethod);

 

    //show the Win32 common print dialog (customized by the hook above)

    bool f = false;

    try

    {

        f = PrintDlg(ref pdlg);

    }

    catch (Exception ex)

    {

        //any exception in the direct Win32 call is most likely caused by a faulty

        //printer driver (we had one issue where the user provided a stack trace

        //pointing to this call that was resolved by uninstalling printer and installing

        //with a new printer driver)

        throw new ApplicationException("The current printer caused a Windows error:\n ", ex);

    }

    if (!f)

    {

        //closed with Cancel, or an error showing the dialog

        long err = CommDlgExtendedError();

        if (err != 0)

        {

            Debug.WriteLine("Windows common dlg error code: " + err);

        }

    }

 

    return f;

}

 

 


Reader comments:
Name: (optional)
Verification text:    
(type as in image next to it)
Comment: max 2,000 characters; for security reasons no active content / no HTML formatting is supported.
Please stick to the subject of the article; comments are reviewed and unrelated / inappropriate ones will be deleted.

On Nov 23, 2009 at 2:36 EST Kumara said:

How do I control the "Print to file" option and to set "Number of copies" (min and max) and disable those controls?

On Oct 2, 2009 at 15:40 EST Hamza said:

Hi, I was wondering why it is not working on my environnement.. I tried to make work under .NET 3.5 and I run windows 7 I wasn t able to run the classic print dialog until I change it from systems.windows.forms to systems.windows.control thanks

On Feb 7, 2008 at 4:43 EST Emin said:

Hi, Thanks for the article. It is the best custom print dialog code that I've seen on the internet so far. However, I also need another customization. This article shows a way to "expand" the print dialog. Is it possible that it can be "cropped"? For example is it possible to display only the "Printer" groupbox?
Copyright 2308 registered users, 21 users online now