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());

Tuesday, April 21, 2009

How to execute (invoke) a function using reflection if we know it's name and class

Have you ever wondered how to do that. This can be useful if you build a custom control and want user to tell you what function to call, for some reason. Code shown bellow.

Public Shared Function CallFunctionByReflection(ByVal className As String, ByVal functionName As String, ByVal arguments As Object())
    Dim theType As Type = Nothing
    Dim theObj As [Object] = Nothing
    Dim theMethodInfo As MethodInfo = Nothing

    theType = System.Type.GetType(className)

    theObj = Activator.CreateInstance(theType)

      theMethodInfo = theType.GetMethod(functionName)

      If arguments IsNot Nothing Then
        Return theMethodInfo.Invoke(theObj, arguments)
        Return theMethodInfo.Invoke(theObj, Nothing)
      End If
    Catch Ex As Exception
      Throw Ex
      Return Nothing
      theType = Nothing
      theObj = Nothing
      theMethodInfo = Nothing
    End Try
  End Function