本文附带的解决方案包含了完整的打印预览库(GridPrintPreviewLib
)和一个带有数据的示例应用程序(TestApp
)。测试数据存储在一个XML文件中,因此不需要额外的设置即可使用示例。
打印和打印预览的基本类是PrintDocument
,扩展了它来创建GridPrintDocument
。GridPrintDocument
是打印的真正引擎。还有GLPrintPreviewControl
和frmPreviewDialog
类。它们是预览的用户界面,用于处理用户交互和页面设置。为了获取预览的图像,使用了GridPrintDocument
的GetPreviewPageInfo()
方法。请注意,在GridPrintDocument
的PrintPreview
中,为PrintController
分配了一个PreviewPrintController
。
private void PrintPreview(bool flagOnlyMeasure)
{
PreviewPrintController preview = new PreviewPrintController();
preview.UseAntiAlias = true;
this.PrintController = preview;
m_FlagOnlyMeasure = flagOnlyMeasure;
base.Print();
m_FlagOnlyMeasure = false;
}
在GLPrintPreviewControl
中,可以看到下面的代码用于生成预览的图像。
public void GeneratePreview()
{
Cursor oldCursor = this.Cursor;
this.Cursor = Cursors.WaitCursor;
m_PrintDocument.Print();
PreviewPageInfo[] p = m_PrintDocument.GetPreviewPageInfo();
previewPages = p;
this.Cursor = oldCursor;
}
已经“替换”了GridPrintDocument
中的默认Print
方法。这是必要的,以强制预览并适应处理。同时,这意味着如果想将结果打印到实际的打印机上,需要生成预览(不一定要显示它),然后使用基础打印方法打印当前结果,如下所示:
m_PrintDocument.PrintController = null;
m_PrintDocument.PrinterSettings.PrinterName = printerName;
((PrintDocument)m_PrintDocument).Print();
该库可以在任何项目中使用。只需要添加引用,无需其他操作。当然,项目中需要有一个DataGridView
。下面可以看到使用frmPreviewDialog
显示DataGridView
打印预览的基本代码。对话框将管理与用户的交互。
dataGridView1是将要打印的DataGridView
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();
为了使用frmPreviewDialog
,需要创建对象并设置ShowUnit
和printDocument
。printDocument
是要预览的GridPrintDocument
,ShowUnit
是设置想要显示的边距(毫米、英寸或显示单位)。在GridPrintDocument
中,有一个名为StepForFit
的新属性。这个值用于在适应操作不完美时减少适应因子。让解释一下。为了将网格适应到n页中,以这种方式计算减少因子:
float scaleX = (m_PageSize.Width * m_FitToHorizontalPages) / m_MaxWidth;
float scaleY = (m_PageSize.Height * m_FitToVerticalPages) / m_MaxHeight;
不幸的是,这个表达式没有考虑到一个列不能分割到不同的页面,所以当应用这些减少因子(scaleX
和scaleY
)时,结果可能超出请求的范围。为了解决这个问题,检查输出是否在范围内:如果不是,通过一个百分比(StepForFit
)减少因子,而0.02F意味着2%,然后重新应用因子以生成新的预览。请看下面的代码:
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);
为了在预览窗口中显示当前页面,进行了一系列的图形操作,然后使用PictureBox
来显示结果。这是必要的,因为如果显示从PreviewPageInfo
方法得到的图像,其大小大于纸张的实际物理大小。然后,需要在具有正确大小的位图中绘制图像,然后使用StretchImage
模式在PictureBox
中显示它。还应用了一个缩放因子进行缩放。所以,请注意以下操作以正确显示图像:
private void ShowCurrentPage()
{
if (m_PreviewPages == null)
{
return;
}
// Dispose previous image
if (pbPreviewImage.Image != null)
{
pbPreviewImage.Image.Dispose();
pbPreviewImage.Image = null;
}
// Create the bitmap
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))
{
// Empty white rectangle
gr.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height);
// Draw preview image
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;
}
最复杂的处理是在GridPrintDocument
的OnPrintPage
中进行的。其中一个是要检查是否有更多的水平或垂直页面。为此,使用了两个不同的标志,将在方法的最后进行检查。请参见以下代码:
...
sizeF = onMeasureCell(e.Graphics, cell, s, m_Font);
// Check Y : use sizeF.Height instead of cellSize.Height
// if you want to measure string and not cell
if (y + sizeF.Height > scaleMarginRB.Y)
if (y + cellSize.Height > scaleMarginRB.Y)
{
hasMoreYPage = true;
break;
}
// Check X : use sizeF.Width instead of cellSize.Width
// if you want to measure string and not cell
if (x + sizeF.Width > scaleMarginRB.X)
if (x + cellSize.Width > scaleMarginRB.X)
{
hasMoreXPage = true;
maxCol = m_ColIndex;
break;
}
...
// Set flag HasMorePages if there're more pages
e.HasMorePages = hasMoreXPage || hasMoreYPage;
...
...
// Start to check new page
while (m_RowIndex <= m_SelArea.EndRow)
{
// Start row for first col
m_ColIndex = m_PrevPageColIndex;
while (m_ColIndex <= m_SelArea.EndCol)
...
...
// If more X page restore start row as this page and col from next col
if (hasMoreXPage)
{
m_RowIndex = m_PrevPageRowIndex;
m_PrevPageColIndex = maxCol;
}
...