Thursday, April 23, 2009

How to print contents of datagridview?

I was looking for an answer on a web since i didn't want to write PrintDocument form as scratch. I found this website to be useful, but when i implemented code there were minor bugs in the code. There was some problem with page numbers when using a print preview and code assumed that data source of a grid was dataset and that all columns were shown, witch was not true in my case. Fixed document class below:

using System;
using System.Data;
using System.Windows.Forms;
using System.Drawing.Printing;
using System.Drawing; 

namespace VA.Gui
  public class PrintGridDocument : PrintDocument
    //Data Members
    private DataGridView m_oDataGrid;
    private int m_nCurrPage;
    private int m_nCurrRow;
    private int m_nColumns;
    private int m_nRows;
    private bool m_bInitialized;
    private int m_nLinesPerPage;
    private int m_nTotalPages;
    private int[] m_nColBounds;

    public Font PrintFont;
    public string Title;

    public PrintGridDocument(DataGridView aGrid)
      : base()
      //Default Values
      m_oDataGrid = aGrid;
      m_nCurrPage = 0;
      m_nCurrRow = 0;
      m_bInitialized = false;

      m_nColumns = m_oDataGrid.ColumnCount;
      m_nRows = m_oDataGrid.RowCount ;

    //Override OnBeginPrint to set up the font we are going to use
    protected override void OnBeginPrint(PrintEventArgs ev)
      //If client has not created a font, create a default font
      // Note: an exception could be raised here, but it is deliberately not
      // being caught because there is nothing we could do at this point!
      if (PrintFont == null)
        PrintFont = new Font("Arial", 9);


    //Override the OnPrintPage to provide the printing logic for the document
    protected override void OnPrintPage(PrintPageEventArgs e)
      m_nCurrPage = 0;
      //Call base method

      //Get the margins 
      int nTextPosX = e.MarginBounds.Left;
      int nTextPosY = e.MarginBounds.Top;

      //Do first time initialization stuff
      if (!m_bInitialized)
        // Calculate the number of lines per page.
        m_nLinesPerPage = (int)(e.MarginBounds.Height / PrintFont.GetHeight(e.Graphics));
        m_nTotalPages = (int)Math.Ceiling((float)m_nRows / (float)m_nLinesPerPage);

        //Create bounding box for columns
        m_nColBounds = new int[m_nColumns];

        //Calculate the correct spacing for the columns
        for (int nCol = 0; nCol < m_nColumns; nCol++)
          //Measure the column headers first
          m_nColBounds[nCol] = (int)e.Graphics.MeasureString(
              m_oDataGrid.Columns[nCol].HeaderText , PrintFont).Width;

          for (int nRow = 0; nRow < m_nRows; nRow++)
            //Compare data to current max
            if (e.Graphics.MeasureString(m_oDataGrid[nCol,nRow].Value.ToString(), PrintFont).Width > m_nColBounds[nCol])
              m_nColBounds[nCol] = (int)e.Graphics.MeasureString(m_oDataGrid[nCol, nRow].Value.ToString(), PrintFont).Width;

          //Just use max possible size if too large
          if (m_nColBounds[nCol] > e.MarginBounds.Width / m_nColumns)
            m_nColBounds[nCol] = e.MarginBounds.Width / m_nColumns;

          //Can't be less than column width
          if (m_nColBounds[nCol] < (int)Math.Round(e.Graphics.MeasureString(m_oDataGrid.Columns[nCol].HeaderText , PrintFont).Width))
            m_nColBounds[nCol] = (int)Math.Round(e.Graphics.MeasureString(m_oDataGrid.Columns[nCol].HeaderText, PrintFont).Width);

        //Move to correct starting page
        if (this.PrinterSettings.PrintRange == PrintRange.SomePages)
          while (m_nCurrPage < this.PrinterSettings.FromPage - 1)
            //Move to next page - advance data to next page as well
            m_nCurrRow += m_nLinesPerPage;
            if (m_nCurrRow > m_nRows)

          if (m_nCurrPage > this.PrinterSettings.ToPage)
            //Don't print anything more

        //Set flag
        m_bInitialized = true;

      //Move to next page

      //Print Title if first page
      if (m_nCurrPage == 1)
        Font TitleFont = new Font("Arial", 15);
        int nXPos = (int)(((e.PageBounds.Right - e.PageBounds.Left) / 2) -
            (e.Graphics.MeasureString(Title, TitleFont).Width / 2));
        e.Graphics.DrawString(Title, TitleFont, Brushes.Black, nXPos, e.MarginBounds.Top - TitleFont.GetHeight(e.Graphics) - 10);

      //Draw page number
      string strOutput = "Page " + m_nCurrPage + " of " + m_nTotalPages;
      e.Graphics.DrawString(strOutput, PrintFont, Brushes.Black, e.MarginBounds.Right - e.Graphics.MeasureString(strOutput, PrintFont).Width,

      //Utility rectangle - use for many drawing operations
      Rectangle aRect = new Rectangle();

      //Loop through data
      for (int nRow = m_nCurrRow; nRow < m_nRows; nRow++)
        //Draw the current row within a shaded/unshaded box
        aRect.X = e.MarginBounds.Left;
        aRect.Y = nTextPosY;
        aRect.Width = e.MarginBounds.Width;
        aRect.Height = (int)PrintFont.GetHeight(e.Graphics);

        //Draw the box
        if (nRow % 2 == 0)
          e.Graphics.FillRectangle(Brushes.LightGray, aRect);

        e.Graphics.DrawRectangle(Pens.Black, aRect);

        //Loop through each column
        for (int nCol = 0; nCol < m_nColumns; nCol++)
          //Set the rectangle to the correct position
          aRect.X = nTextPosX;
          aRect.Y = nTextPosY;
          aRect.Width = m_nColBounds[nCol];
          aRect.Height = (int)PrintFont.GetHeight(e.Graphics);
          //Print the data
          e.Graphics.DrawString(m_oDataGrid[nCol,nRow].Value.ToString(), PrintFont, Brushes.Black, aRect);
          //Advance the x Position counter
          nTextPosX += m_nColBounds[nCol];

        //Reassign the x position counter
        nTextPosX = e.MarginBounds.Left;
        //Move the y position counter down a line
        nTextPosY += (int)PrintFont.GetHeight(e.Graphics);

        //Check to see if we have reached the line limit - move to a new page if so
        if (nRow - ((m_nCurrPage - 1) * m_nLinesPerPage) == m_nLinesPerPage)
          //Save the current row
          m_nCurrRow = ++nRow;
          e.HasMorePages = true;

Usage is simple:

private void buttonCtl1_Click(object sender, EventArgs e)
      //Setup the document to print
      PrintGridDocument aDoc = new PrintGridDocument(dataGridViewCtl1 );
      aDoc.Title = "Employee Report";
      printDialog1 .Document = aDoc;
      if (printDialog1.ShowDialog() == DialogResult.OK)
        //Display the print preview dialog
        aDoc.DefaultPageSettings = new PageSettings(printDialog1.PrinterSettings);

        //comment next two lines if do not want to use print preview
        printPreviewDialog1.Document = aDoc;

        //uncomment lines below if not using print preview
        //  //Print the document
        //  aDoc.Print();
        //catch (Exception ex)
        //  //Display any errors
        //  MessageBox.Show(ex.ToString());

No comments: