Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

DataGridView Print/Print Preview Solution - Part I

4.85/5 (14 votes)
16 May 2009CPOL5 min read 167.1K   14.8K  
This article is the first of two articles in which I want to show a solution for the Print Preview of the DataGridView object. One of the goals of my solution is to to print the DataGridView keeping its styles automatically.

Introduction

This article is the first of two articles that I am writing about Print Preview for the DataGridView object. I’ve seen different articles and code about this, and I hope to give some contribution to it. When I’ started to write this piece of code, I had three different goals in my mind:

  • To have a class that prints a DataGridView keeping its styles;
  • To manage a set of possibilities like margins, fit to pages, print a part of the table, and so on;
  • To have a custom window for the preview (I’ll show that in the second article).

Also, I’ve tried to write a class that is possible to extend and customize in order to change the output result in a way like you can override the Paint method in DataGridViewCell to change the painting. The solution attached to the article contains the library for the Print Preview (GridPrintPreviewLib) and a sample application with data (TestApp). Test data are on 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. In the OnPrintPage overridden method, you can see the logic to check if it’s possible to print in the page and then print it. Currently, the engine doesn’t manage images inside the DataGridView. Only the text is printed using its styles. You can override my methods to create a new class that prints images if you need this option.

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 PrintPreviewDialog.

C#
// dataGridView1 is the DataGridView to print
GridPrintDocument doc = new GridPrintDocument(this.dataGridView1, 
                        this.dataGridView1.Font, true);
doc.DocumentName = "Preview Test";

PrintPreviewDialog printPreviewDialog = new PrintPreviewDialog();
printPreviewDialog.ClientSize = new Size(400, 300);
printPreviewDialog.Location = new Point(29, 29);
printPreviewDialog.Name = "Print Preview Dialog";
printPreviewDialog.UseAntiAlias = true;
printPreviewDialog.Document = doc;
printPreviewDialog.ShowDialog();
doc.Dispose();
doc = null;

Print a selected part of the grid

The engine is designed to print with different options. One of them is the possibility to print just a part of the grid. To do that, I’ve created the GridSelectedArea class that represents an area of the Grid. You define the start row and column and the final row and column as well. You can change the previous example by adding the use of the GridSelectedArea and passing it to the GridPrintDocument, setting the SelectedArea attribute. Please note that this attribute is never set to null; the GridPrintDocument constructor fills it with a GridSelectedArea that covers all the grid. For this reason, you cannot use the same instance of GridPrintDocument if you change the DataGridView linked with it. Better to say, you can, but you need to manually change the SelectedArea attribute as well. See an example of the GridSelectedArea below.

C#
// dataGridView1 is the DataGridView to print 
// selArea is the part that I want to print. 
// In this case from column 1 row 2 to column 10 and third last row. 
GridPrintDocument doc = new GridPrintDocument(this.dataGridView1, 
                            this.dataGridView1.Font, true);

GridSelectedArea selArea = new GridSelectedArea(1, 2, 10, 
                           dataGridView1.RowCount - 3);
doc.SelectedArea = selArea; 
doc.DocumentName = "Preview Test"; 
PrintPreviewDialog printPreviewDialog = new PrintPreviewDialog(); 
printPreviewDialog.ClientSize = new Size(400, 300); 
printPreviewDialog.Location = new Point(29, 29); 
printPreviewDialog.Name = "Print Preview Dialog"; 
printPreviewDialog.UseAntiAlias = true; 
printPreviewDialog.Document = doc; 
printPreviewDialog.ShowDialog(); 
doc.Dispose(); doc = null;

Fit the grid into one page

The GridPrintDocument can fit the grid in one or more pages. Unfortunately, to calculate the x and y factors for the fit, the engine needs to print the grid once before. So, the example below shows the normal grid at the beginning and then shows the grid fitting to a page. A better result is obtained with a custom preview that you’ll see in the second part of this article.

C#
// dataGridView1 is the DataGridView to print 
GridPrintDocument doc = new GridPrintDocument(this.dataGridView1, 
  this.dataGridView1.Font, true); 
doc.DocumentName = "Preview Test"; 
PrintPreviewDialog printPreviewDialog = new PrintPreviewDialog(); 
printPreviewDialog.ClientSize = new Size(400, 300); 
printPreviewDialog.Location = new Point(29, 29); 
printPreviewDialog.Name = "Print Preview Dialog"; 
printPreviewDialog.UseAntiAlias = true; 
printPreviewDialog.Document = doc; 
printPreviewDialog.ShowDialog();

// calc factor for fit grid in one page 
float scale = doc.CalcScaleForFit(); 
doc.ScaleFactor = scale; 

// show the grid again 
printPreviewDialog = new PrintPreviewDialog(); 
printPreviewDialog.ClientSize = new Size(400, 300); 
printPreviewDialog.Location = new Point(29, 29); 
printPreviewDialog.Name = "PrintPreviewDialog1"; 
printPreviewDialog.UseAntiAlias = true; 
printPreviewDialog.Document = doc; 
printPreviewDialog.ShowDialog(); 
doc.Dispose(); 
doc = null;

However, for fitting to pages, you have the following methods: CalcScaleForFit, if you want to fit to n1 horizontal pages and n2 vertical pages, and CalcScaleForFit, if you want to fit all in one page. The factors obtained from these methods should be passed on to the engine using ScaleFactorX, ScaleFactorY, or ScaleFactor.

Other print options

Using the DefaultPageSetting of the GridPrintDocument, you can change the print orientation, the margins, and the pages (from/to) that you want to print as well.

C#
// dataGridView1 is the DataGridView to print 
GridPrintDocument doc = new GridPrintDocument(this.dataGridView1, 
                        this.dataGridView1.Font, true); 

doc.DocumentName = "Preview Test"; 
doc.DefaultPageSettings.Landscape = true; 
doc.DefaultPageSettings.PrinterSettings.FromPage = 1; 
doc.DefaultPageSettings.PrinterSettings.ToPage = 3;

PrintPreviewDialog printPreviewDialog = new PrintPreviewDialog(); 
printPreviewDialog.ClientSize = new Size(400, 300); 
printPreviewDialog.Location = new Point(29, 29); 
printPreviewDialog.Name = "Print Preview Dialog"; 
printPreviewDialog.UseAntiAlias = true; 
printPreviewDialog.Document = doc; 
printPreviewDialog.ShowDialog(); 
doc.Dispose(); 
doc = null;

Points of interest

The print process is performed on the OnPrintPage method in GridPrintDocument class. The logical flow of the print process is described as follows:

  1. If the page comes before the DefaultPageSettings.PrinterSettings.FromPage value, skip it;
  2. Apply scale;
  3. Calculate the vertical space for the header (if it’s present) and leave it on the page;
  4. Draw a horizontal cell checking if more pages in the horizontal axes are necessary;
  5. Draw the next row checking if more pages in the vertical axes are necessary;
  6. On first page, draw the header (if it’s present);
  7. If the next page comes after the DefaultPageSettings.PrinterSettings.ToPage value, skip it.

The flow calls for a set of private and protected methods in order to resolve all the calculations and perform the print process. The protected virtual methods are designed to permit overriding them in order to customize the print output. In detail, these methods are:

onMeasureCellTo measure the cell size
onPrepareDrawCellTo prepare all the necessary data for drawing the cell
onDrawCellTo draw the cell
onMeasureColumnHeaderHeightTo measure the vertical size of the header
onPrepareColumnHeaderTo prepare all the necessary data for drawing the header
onDrawColumnHeaderTo draw the header

I suggest not overriding the onPrepare methods because it should be necessary to implement a set of logic inside. Also, the onMeasure and onDraw methods could be enough to customize printing. Anyway, you have them if you want some different results! Below, you can see the code for the onDraw methods.

C#
protected virtual void onDrawCell(Graphics g, string s, RectangleF layoutRect, 
                  StringFormat format, Font font, Brush brush, Brush brushBack, 
DataGridViewAdvancedBorderStyle borderStyle) 
{
  // Draw 
  g.FillRectangle(brushBack, layoutRect); 
  g.DrawString(s, font, brush, layoutRect, format); 
  // Draw box drawBox(g, layoutRect, borderStyle); 
}

As you can see, the method just draws the cell using the passed parameters. In detail, it draws the background, then the string inside, and the box around if it’s required.

C#
protected virtual void onDrawColumnHeader(Graphics g, string s, RectangleF layoutRect, 
          StringFormat format, Font font, DataGridViewAdvancedBorderStyle borderStyle) 
{ 
  g.DrawString(s, font, Brushes.Black, layoutRect, format); 
  // Draw box 
  drawBox(g, layoutRect, borderStyle); 
}

The method for drawing the header is called for every header label, and it draws the header using the passed parameters. In detail, it draws the string inside and the box around if it’s required. Please find below an example of the onDrawCell override:

C#
protected override void onDrawCell(System.Drawing.Graphics g, string s, 
  System.Drawing.RectangleF layoutRect, System.Drawing.StringFormat format, 
  System.Drawing.Font font, System.Drawing.Brush brush, System.Drawing.Brush brushBack, 
  System.Windows.Forms.DataGridViewAdvancedBorderStyle borderStyle) 
{ 
  borderStyle.Left = System.Windows.Forms.DataGridViewAdvancedCellBorderStyle.Single; 
  borderStyle.Right = System.Windows.Forms.DataGridViewAdvancedCellBorderStyle.Single; 
  base.onDrawCell(g, s, layoutRect, format, font, brush, brushBack, borderStyle); 
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)