Editable GridView Control in C# and .NET - Part-III Printing the GridView

In our last two articles, we talked about how to create an editable GridView and how to make it persistent in XML. This article covers how we can take advantage of the .NET framework to print out our GridView to an available printer.
(To see the other articles in our series,  Go to Editable GridView Control in C# and .NET - Part I  and Editable GridView Control in C# and .NET - Part II).
Figure 1 - Printing the GridView
.Net gives us a few controls that make printing quite easy and handle much of the details of driving the printer internally. In fact,  most of our responsibility in printing the GridView control lies in graphically drawing its contents. The drawing of the GridView control is accomplished using GDI+.  Below is the routine for drawing the GridView. The Print method takes a Graphics object (in the case of printing this is a Printer Graphics Object).  It utilizes the Graphics Object to draw the lines, colors and text necessary to construct our grid.  The hard part of drawing the grid is mathematically mapping what is on our current grid control to our drawing surface. Also we have to figure out what methods we can use from the ListView to give us our cell borders and locations of them. The Print method first prints the column header, then the cells of the GridView, and finally the gridlines and boundary of the GridView.
  1. [DispId(22)]  
  2. public void Print(Graphics g) {  
  3.  Rectangle frame = this.Bounds;  
  4.  // Calculate the Height of a Cell in the Grid  
  5.  int cellHeight = GetCellBounds(1, 1).Height;  
  6.  // Calculate the values needed to locate the Extents of the Grid  
  7.  int rightExtent = GetCellBounds(1, NumberOfColumns).Right + Bounds.X;  
  8.  int bottomExtent = GetCellBounds(NumberOfRows, 1).Bottom + Bounds.Y;  
  9.  // Create a string format object that aligns the string in the center of the rectangle  
  10.  StringFormat sfcenter = new StringFormat();  
  11.  sfcenter.Alignment = StringAlignment.Center;  
  12.  // Loop through all the columns and draw the column header text and background color  
  13.  for (int j = 1; j <= this.NumberOfColumns; j++) {  
  14.   string strText = listView1.Columns[j - 1].Text;  
  15.   Color aColor = Color.LightGray;  
  16.   Color aTextColor = Color.Black;  
  17.   Rectangle cellFrame = GetCellBounds(1, j);  
  18.   cellFrame.Offset(Bounds.X, Bounds.Y); // offset to the absolute position on the form  
  19.   int nTop = cellFrame.Top;  
  20.   cellFrame.Offset(0, -(cellFrame.Y - Bounds.Y));  
  21.   cellFrame.Height += (nTop - Bounds.Top);  
  22.   g.FillRectangle(new SolidBrush(aColor), cellFrame);  
  23.   g.DrawString(strText,  
  24.    listView1.Font, new SolidBrush(aTextColor), cellFrame, sfcenter);  
  25.  }  
  26.  // Loop through all of the cells in the grid, draw background color, text color, and text for each cell  
  27.  for (int i = 1; i <= this.NumberOfRows; i++) {  
  28.   for (int j = 1; j <= this.NumberOfColumns; j++) {  
  29.    string strText = this.GetCell(i, j);  
  30.    Color aColor = this.GetCellColor(i, j);  
  31.    Color aTextColor = this.GetCellTextColor(i, j);  
  32.    Rectangle cellFrame = GetCellBounds(i, j);  
  33.    cellFrame.Offset(Bounds.X, Bounds.Y);  
  34.    g.FillRectangle(new SolidBrush(aColor), cellFrame);  
  35.    g.DrawString(strText, listView1.Font, new SolidBrush(aTextColor), cellFrame,  
  36.     sfcenter);  
  37.   }  
  38.  }  
  39.  // Loop through all the cells in the grid again. Draw all of the lines that form the borders for the cells.  
  40.  for (int i = 1; i <= this.NumberOfRows; i++) {  
  41.   Rectangle r1 = GetCellBounds(i, 1);  
  42.   r1.Offset(Bounds.X, Bounds.Y);  
  43.   g.DrawLine(Pens.Black, Bounds.X, r1.Top, rightExtent, r1.Top);  
  44.   for (int j = 1; j <= this.NumberOfColumns; j++) {  
  45.    Rectangle cellFrame = GetCellBounds(i, j);  
  46.    cellFrame.Offset(Bounds.X, Bounds.Y);  
  47.    if (i == 1) {  
  48.     g.DrawLine(Pens.Black, cellFrame.Left, Bounds.Y, cellFrame.Left, bottomExtent);  
  49.    }  
  50.    if (j == NumberOfColumns) {  
  51.     g.DrawLine(Pens.Black, cellFrame.Right, Bounds.Y, cellFrame.Right, bottomExtent);  
  52.    }  
  53.   }  
  54.  }  
  55.  // Draw the Border around the entire grid  
  56.  Rectangle rFrame = Bounds;  
  57.  rFrame.Width -= (Bounds.Right - rightExtent);  
  58.  rFrame.Height -= (Bounds.Bottom - bottomExtent);  
  59.  g.DrawRectangle(Pens.Black, rFrame);  
  60. }
As shown in the code above, we created a method GetCellBounds that gets the absolute boundaries of the cell with the listView as a frame of reference. This method utilizes the Bounds property of the ListViewItem class along with the Width property of the ColumnHeader class to determine the location of the cell in the grid.
  1. public Rectangle GetCellBounds(int aRow, int aColumn) {  
  2.  // Get the boundary of an entire row in the ListView corresponding to the aRow index passed  
  3.  Rectangle r = listView1.Items[aRow - 1].Bounds;  
  4.  // Get the width of the column indicated by the column index passed  
  5.  int colWidth = listView1.Columns[aColumn - 1].Width;  
  6.  // Find the starting position of the column using the GetStartOfColumn function in the GridView  
  7.  int startCol = GetStartOfColumn(aColumn);  
  8.  // Shrink the ListView Item Boundary rectangle to only contain the boundary of the cell passed  
  9.  r.X += startCol;  
  10.  r.Width = colWidth;  
  11.  return r;  
  12. }  
  13. // The GetStartOfColumn computes a sum of all the column widths up to (but not including) the index of  
  14. // the column passed to the function. This value is the position of the column in the grid  
  15. public int GetStartOfColumn(int aColumn) {  
  16.  int sum = 0;  
  17.  for (int i = 1; i < aColumn; i++) {  
  18.   sum += listView1.Columns[i - 1].Width;  
  19.  }  
  20.  return sum;  
  21. }
That's all there is to drawing the GridView in GDI+. Now we need to take advanage of the Print method in the GridView to print our GridView to the printer. In our form application we need to drag a PrintDocument object and a PrintPreviewDialog object onto the form. This gives us access to the objects printDocument1 and printPreviewDialog1 (although you can change these names if you wish in the properties window. In the properties window of printPreviewDialog1, assign the Document property to printDocument as shown in Figure 2 below:
Figure 2 - Assigning the print document to the print preview dialog
Double click on the printDocument1 object shown in your design view to wire up the PrintPage event shown in the property window of printDocument1  in Figure 3.
Figure 3 - Wiring up the PrintPage event
This will create an event handler called printDocument1_PrintPage which you can now place the necessary code to print your GridView shown below:
  1. private void printDocument1_PrintPage(object sender,System.Drawing.Printing.PrintPageEventArgs e)  
  2. {  
  3.     // Print the GridView by passing the Printer Graphics Object thats passed into the PrintPage event handler into  
  4.     // the Print method of the GridView.  
  5.     gridView1.Print(e.Graphics);  
  6. }
Now to activate the PrintPage event, we need to either print or bring up a print preview dialog to simulate a print. Below is the code for bringing up the print preview dialog from a menu event handler called PrintMenu_Click. Note that we size the print preview dialog to be at least the size of the form and we force a zoom to 100%.
  1. private void PrintMenu_Click(object sender, System.EventArgs e) {  
  2.  // size the dialog to be as big as the form (+ a little more)  
  3.  printPreviewDialog1.SetBounds(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width + 50, ClientRectangle.Height + 50);  
  4.  // Zoom the preview to 100%  
  5.  printPreviewDialog1.PrintPreviewControl.Zoom = 1.0;  
  6.  // Show the dialog  
  7.  if (printPreviewDialog1.ShowDialog() == DialogResult.OK) {}  
  8. }
Once you bring up the print preview, you can print directly from this dialog.  If you wish to print directly to the printer without bringing up the preview dialog you can replace the code in the event handler above with the following:
  1. private void PrintMenu_Click(object sender, System.EventArgs e)  
  2. {  
  3.     printDocument1.Print();  
  4. }
Either The Print method of the PrintDocument class or the ShowDialog of the PrintPreviewDialog class can be used to activate the PrintPage event.
In this article, we showed you how you can take your GridView control and output an image of it to the printer utilizing GDI+ and the print components of the .NET framework. In Part IV of this series, we'll show you how to export the contents of your GridView to an Excel spreadsheet.