Extending C# PrintDialog Part II






4.07/5 (7 votes)
Printing Odd/Even pages in C#

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 the only limit. In this article, I will present a print dialog with an extended panel having: even/odd print page combobox, page setting button and an image.
Overview
PrintDialogEx
serves as a wrapper to systems PrintDlg(common dialog)
, which uses P/Invoke methods to access the Win32 system's low-level APIs. Hook the dialog window mainproc and attach our extended user control panel when it receives a WM_INIT_DIALOG
event notification. For a more detailed explanation, please refer to my previous article Extending C# PrintDialog Part I.
The main logic in achieving printing of odd/even pages is a general printing framework which, abstracts each print page into a C# class having common properties for a print page; included in these properties is a flag which indicates if the page should be printed or skipped during printing. The below figure depicts this concept:

Generic Printing Framework
The framework was created with a general printing capability in mind; which means printing any sort of data namely: texts, images, tables having variable formatting/sizes and can accept any type of source data. For this article's purpose, only text printing is currently supported. However, the framework can be used as a base skeleton for user customizations and improvements.

PrintDocumentEx
is a customized print document object derived from System.Drawing.PrintDocument
. It overrides and handles the following events: OnBeginPrint
, OnEndPrint
, OnPrintPage
. These events are automatically called by the system. For a more detailed information, please refer to: Using PrintDocument.
OnBeginPrint
- is called after the Print
method is called and before the first page of the document prints. This is where we inject our code to create each PrintSection
, layouts and calculate its size and add it to the next available PrintPage
from PrintDocumentEx
list container. Using a well-known design pattern called: Builder by Gamma, Helm, Johnson and Vlissides; to create different print document layout and presentations.
protected override void OnBeginPrint(PrintEventArgs e) {
base.OnBeginPrint(e);
// reset current page index
this.m_nCurrentPage = 0;
// remove all previous print pages.
this.m_oPages.Clear();
if (this.DocBuilder != null) {
// create the print document page layouts.
this.DocBuilder.Build(this);
}
// initialize page skip flag attribute
for(int nPage = 0; nPage < this.PageCount; nPage++) {
Page oPage = this.GetPage(nPage);
oPage.Skip = true;
if ( this.m_bEvenPageOnly ) {
// set page skip flag to false if page num is even
oPage.Skip = ((nPage % 2) == 0) ? true : false;
}
else if ( this.m_bOddPageOnly ) {
// set page skip flag to false if page num is odd
oPage.Skip = ((nPage % 2) == 0) ? false : true;
}
else {
// prints all page
oPage.Skip = false;
}
}
}
The document builder creates the appropriate concrete section object based on the print data type. After all the print pages initialization, we iterate all print pages and set its skipped flag value to true
or false
, depending on our PrintDocumentEx OddPageOnly
or EvenPageOnly
property value.
OnPrintPage
- is called before a page prints. Here we iterate through our print page container list and manually draw each section contained on each page to a GDI graphics object(PrintPageEventArgs.Graphics
).
protected override void OnPrintPage(PrintPageEventArgs e)
{
try {
base.OnPrintPage(e);
// get the page print area
Graphics gdiPage = e.Graphics;
float leftMargin = e.MarginBounds.Left;
float topMargin = e.MarginBounds.Top;
Rectangle oArea = e.MarginBounds;
// get the next page, labelled for printing.
Page oPage = this.GetPage(this.m_nCurrentPage++);
while( oPage.Skip && (this.m_nCurrentPage < this.PageCount) ) {
oPage = this.GetPage(this.m_nCurrentPage++);
}
if ( (null != oPage) && !oPage.Skip ) {
gdiPage.DrawString("Page " + this.m_nCurrentPage,
new Font("Verdana", 10), Brushes.Black,
leftMargin, 0 );
oPage.OnPrintPage(gdiPage, this.m_oPrintArea );
}
// checks if there are more pages to print
e.HasMorePages = this.HasMorePages();
}
catch { // handle all unknown errors during printing.
e.HasMorePages = false;
}
} // OnPrintPage
OnEndPrint
- is called when the last page of the document has printed. nothing fancy here, we just removed all pages contained in our list.
Using the Code
Client application needs only to create a PrintDialogEx
, associate it to an instance of PrintDocumentEx
before calling its ShowDialog()
. By default, we can set to print odd/even pages by setting the PrintDocumentEx
's OddPageOnly
or EvenPageOnly
to true
. Below is a sample code snippet to do this:
PrintDialogEx oPrintDlg = new PrintDialogEx();
PrintDocumentEx oDocument = PrintDocumentEx();
// set to print odd pages only.
oDocument.OddPageOnly = true;
// set the dialogs owner window(optional)
// parent handle should be a handle to a window Form
oPrintDlg.Parent = this.Handle;
// associate this dialog to a print document instance.
oPrintDlg.Document = oDocument;
// Shows the print dialog extended control panel by default.
oPrintDlg.UseEXDialog = true;
// display the print dialog with extended panel control
DialogResult nResult = oPrintDlg.ShowDialog();
Point of Interests
- Represent any source print data in an XML form
References
- Printing Overview, MSDN Documentation
- Extend the Common Dialog Boxes, MSDN Tutorials
History
- 2006.12.18 - Original version