Introduction
Have you ever had to do a printing system for your application and added all the computing logic into the printDocument
? Well, in that case, I think you were exactly at the point I was when I thought of this custom PrintPreviewDialog
.
So, what was the problem? Simple, the PrintPreviewDialog
generates previews of the document it receives, and shows them to the user. Nothing wrong with this. When the user presser the Print button, the dialog starts generating again and draws all the pages again. This is useful when you deal with a database that is prone to changes every second. But when you have the user satisfied with what he sees on the preview and the job is a hard one (with all the database reading logic in it - let's say, with 100 pages and 3 seconds for a page), this tends to be a headache.
The solution
The solution is to inherit the PrintPreviewDialog
, and make sure it prints from the previews generated previously. Sounds easy, but there is no other way to do that except using Reflection...
Behind the scenes
One of the main problems was to intercept the printButton
clicks from the toolbar that is on the top of the dialog. Adding another Click
handler to the toolbar does not help a bit because the old handler (from the base class) still does its job (prints from the PrintDocument
). My solution was to remove the button from the toolbar and add my PrintButton
that looked exactly like the previous one. Now, adding a new handle to the toolbar that checks if the button pressed was my new button was enough because the old button didn't have any chance of being clicked.
public SpeedUpPrintPreviewDialog() : base()
{
Type t = typeof(PrintPreviewDialog);
FieldInfo fi = t.GetField("toolBar1",
BindingFlags.Instance |
BindingFlags.NonPublic);
FieldInfo fi2 = t.GetField("printButton",
BindingFlags.Instance |
BindingFlags.NonPublic);
ToolBar toolBar1 = (ToolBar)fi.GetValue(this);
ToolBarButton printButton =
(ToolBarButton)fi2.GetValue(this);
printButton.Visible = false;
myPrintButton = new ToolBarButton();
myPrintButton.ToolTipText = printButton.ToolTipText;
myPrintButton.ImageIndex = 0;
ToolBarButton[] oldButtons =
new ToolBarButton[toolBar1.Buttons.Count-1];
for(int i = 0 ; i < oldButtons.Length ; i++)
oldButtons[i] = toolBar1.Buttons[i+1];
toolBar1.Buttons.Clear();
toolBar1.Buttons.Add(myPrintButton);
for(int i = 0 ; i < oldButtons.Length ; i++)
toolBar1.Buttons.Add(oldButtons[i]);
toolBar1.ButtonClick +=
new ToolBarButtonClickEventHandler(toolBar1_Click);
innerPrintDocument = new PrintDocument();
innerPrintDocument.BeginPrint +=
new PrintEventHandler(innerPrintDocument_BeginPrint);
innerPrintDocument.PrintPage +=
new PrintPageEventHandler(innerPrintDocument_PrintPage);
}
The next step was to implement a custom prinDocument
object that printed from the previews generated by the dialog. Obtaining the previews from the dialog was kind of hard because they are private members, so Reflection was necessary once again...
private int NumberOfPages()
{
Type t = typeof(PrintPreviewControl);
FieldInfo pageInfo = t.GetField("pageInfo",
BindingFlags.Instance | BindingFlags.NonPublic);
PreviewPageInfo[] infos = (PreviewPageInfo[])
pageInfo.GetValue(this.PrintPreviewControl);
return infos.Length;
}
private Image GetImageForPage(int pageNumber)
{
Type t = typeof(PrintPreviewControl);
FieldInfo pageInfo = t.GetField("pageInfo",
BindingFlags.Instance | BindingFlags.NonPublic);
PreviewPageInfo[] infos = (PreviewPageInfo[])
pageInfo.GetValue(this.PrintPreviewControl);
return infos[pageNumber].Image;
}
Now, having the number of pages in the preview, the images from the preview, and having a handle that knows when the user wants to print, the next and final step was to implement my printDocument
to render each page from the preview.
private void innerPrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.DrawImage(GetImageForPage(
innerPrintDocument_pageNumber), 0.0f, 0.0f);
if(innerPrintDocument_pageNumber == (NumberOfPages() - 1))
e.HasMorePages = false;
else
{
innerPrintDocument_pageNumber++;
e.HasMorePages = true;
}
}
Using the code
The control is easy to use, handle it like the PrintPreviewDialog
. I hope somebody finds this of use, because it helped me.