Introduction
This article is the second of two articles that I wrote about Print Preview for the DataGridView
object. In the first one, I've written about how to use a new class, the GridPrintDocument
, with the standard PrintPreviewDialog
.
Now I'm going to show you how to create a personal preview dialog and how to use it.
The solution attached to the article contains the full library for the Print Preview (GridPrintPreviewLib
) and a sample application with data (TestApp
). Test data are in an XML file, so no settings are necessary to use the sample.
Background
The basic class for the Print and Print Preview is the PrintDocument
that I've extended to create the GridPrintDocument
. The GridPrintDocument
is the real engine for the printing. There are also the GLPrintPreviewControl
and frmPreviewDialog
classes. They is the user interface for the preview and to handle the user interaction for page settings. To get the image for the preview, I've used the GetPreviewPageInfo()
method of the GridPrintDocument
. Please note that in the GridPrintDocument
‘s PrintPreview
, a PreviewPrintController
is assigned to the PrintController
of the GridPrintDocument
. See the following code:
private void PrintPreview(bool flagOnlyMeasure)
{
PreviewPrintController preview = new PreviewPrintController();
preview.UseAntiAlias = true;
this.PrintController = preview;
m_FlagOnlyMeasure = flagOnlyMeasure;
base.Print();
m_FlagOnlyMeasure = false;
}
In the GLPrintPreviewControl
, you can see the code below to generate the images for the preview.
public void GeneratePreview()
{
Cursor oldCursor = this.Cursor;
this.Cursor = Cursors.WaitCursor;
m_PrintDocument.Print();
PreviewPageInfo[] p = m_PrintDocument.GetPreviewPageInfo();
previewPages = p;
this.Cursor = oldCursor;
}
I've “replaced” the default Print
method in GridPrintDocument
of course. This is necessary to force preview and fit elaboration. Also, it means that if you want to print the result to a real printer, you need to generate the preview (it’s not necessary to show it) and then print the current result by using the base print method as below:
m_PrintDocument.PrintController = null;
m_PrintDocument.PrinterSettings.PrinterName = printerName;
((PrintDocument)m_PrintDocument).Print();
Using the Code
The library can be used inside every project. It’s only necessary to add references and nothing more. However, you need to have a DataGridView
in your project, of course. Below, you can see the basic code to show the Print Preview of a DataGridView
using the frmPreviewDialog
. The dialog will manage the interaction with the user.
GridPrintDocument doc = new GridPrintDocument
(this.dataGridView1, this.dataGridView1.Font, true);
doc.DocumentName = "Preview Test";
doc.StepForFit = 0.02f;
frmPreviewDialog frmDialog = new frmPreviewDialog();
frmDialog.ShowUnit = PrinterUnit.TenthsOfAMillimeter;
frmDialog.printDocument = doc;
frmDialog.ShowDialog(this);
frmDialog.Dispose();
doc.Dispose();
In order to use the frmPreviewDialog
, you need to create the object and set ShowUnit
and printDocument
. printDocument
is the GridPrintDocument
to preview and ShowUnit
is to set how you want to show the margins (millimeters, inches or display unit). In GridPrintDocument
, there’s a new attribute named StepForFit
. This value is used to reduce the fit factor when the fit action is not perfect. Let me explain. To fit the grid in n pages, I calc a reduce factor in this way:
float scaleX = (m_PageSize.Width * m_FitToHorizontalPages) / m_MaxWidth;
float scaleY = (m_PageSize.Height * m_FitToVerticalPages) / m_MaxHeight;
Unfortunately this expression doesn't consider that a column cannot split into different pages, so it’s possible that when applying these reduce factors (scaleX
and scaleY
), the result is out of the requested range. To resolve this problem, I check if the output is inside the range: if it’s not, I reduce the factors by a percentage (StepForFit
), while 0.02F means 2%, and then reapply the factor for generating a new preview. See the following piece of code:
bool flag = false;
do
{
flag = false;
ResetForRePrint();
m_ScaleX = scaleX;
m_ScaleY = scaleY;
PrintPreview();
if (m_XPageCount > m_FitToHorizontalPages)
{
flag = true;
scaleX *= (1f - m_StepForFit);
}
if (m_YPageCount > m_FitToVerticalPages)
{
flag = true;
scaleY *= (1f - m_StepForFit);
}
} while (flag);
Points of Interest
To show the current page in the preview window, I've done a set of operations in graphics and then used a PictureBox
to show the result. This is necessary because if you show the image got from PreviewPageInfo
methods, its size is greater than the real physical size of the paper. Then, it’s necessary to draw the image in a bitmap with the correct size and then show it in a PictureBox
using the StrechImage
mode. A scale factor for the zoom is also applied. So, please note the following action in order to show the image correctly:
- Create a bitmap using the physical size and pixel format of the page
- Get a graphics from the bitmap
- Clear the bitmap by drawing a white rectangle
- Draw the image of the current page
- Set size and size mode to the
PictureBox
- Set the created bitmap as the image of the
PictureBox
private void ShowCurrentPage()
{
if (m_PreviewPages == null)
{
return;
}
if (pbPreviewImage.Image != null)
{
pbPreviewImage.Image.Dispose();
pbPreviewImage.Image = null;
}
Bitmap bitmap = new Bitmap(m_PreviewPages[m_Currentpage].PhysicalSize.Width,
m_PreviewPages[m_Currentpage].PhysicalSize.Height,
m_PreviewPages[m_Currentpage].Image.PixelFormat);
using (Graphics gr = Graphics.FromImage(bitmap))
{
gr.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height);
gr.DrawImage(m_PreviewPages[m_Currentpage].Image, 0, 0,
bitmap.Width, bitmap.Height);
}
double scale = CalcZoomFactorForFit(bitmap);
double maxWidth = bitmap.Width;
double maxHeight = bitmap.Height;
maxWidth *= scale;
maxHeight *= scale;
pbPreviewImage.Width = (int)maxWidth;
pbPreviewImage.Height = (int)maxHeight;
pbPreviewImage.Left = (pPreview.Width - pbPreviewImage.Width) / 2;
pbPreviewImage.Top = (pPreview.Height - pbPreviewImage.Height) / 2;
pbPreviewImage.SizeMode = PictureBoxSizeMode.StretchImage;
pbPreviewImage.Image = bitmap;
}
The most complicated elaborations are performed inside the GridPrintDocument
‘s OnPrintPage
. One of these is to check if there're more horizontal or vertical pages. To do that, I've used two different flags that I'll check at the end of the methods. Please see below:
....
sizeF = onMeasureCell(e.Graphics, cell, s, m_Font);
if (y + cellSize.Height > scaleMarginRB.Y)
{
hasMoreYPage = true;
break;
}
if (x + cellSize.Width > scaleMarginRB.X)
{
hasMoreXPage = true;
maxCol = m_ColIndex;
break;
}
....
e.HasMorePages = hasMoreXPage || hasMoreYPage;
....
One more elaboration is to draw the next horizontal page correctly. In this case, I've used 2 variables to know from which row and column I need to start in the next page. m_RowIndex
is the current index of row, so I go back replacing its values with the value of m_PrevPageRowIndex
. m_PrevPageColIndex
is the last column in the previous page, so I set it with the maxCol
(max column in this page). When I start the new page, I'll use the m_RowIndex
and m_PrevPageColIndex
values.
....
while (m_RowIndex <= m_SelArea.EndRow)
{
m_ColIndex = m_PrevPageColIndex;
while (m_ColIndex <= m_SelArea.EndCol)
....
....
if (hasMoreXPage)
{
m_RowIndex = m_PrevPageRowIndex;
m_PrevPageColIndex = maxCol;
}
....
History
- 18th June, 2009: Initial post