Click here to Skip to main content
15,900,378 members
Articles / Web Development / HTML

A Class to Print and Print Preview a DataGrid or DataGridView Control

Rate me:
Please Sign up or sign in to vote.
4.76/5 (38 votes)
24 Jan 2016CPOL2 min read 483.2K   9.8K   131   127
A class to add to your application to generate nicely formatted prints from a DataGrid.

Introduction

The System.Windows.Forms.DataGrid control is one of the most powerful and frequently used controls in the .NET arsenal and this class (DataGridPrinter) adds functionality to allow you to generate print and print previews from your datagrid in a tidy and customizable layout.

Image 1

To Draw a Grid, First Draw a Square

Image 2

The basic operation when drawing a datagrid is to draw a rectangle and fit text inside it. To do this, I have a single subroutine called DrawCellString which draws a string inside a cell according to the alignment and format parameters passed in.

VB
Public Function DrawCellString(ByVal s As String, _
      ByVal HorizontalAlignment As CellTextHorizontalAlignment, _
      ByVal VerticalAlignment As CellTextVerticalAlignment, _
      ByVal BoundingRect As Rectangle, _
      ByVal DrawRectangle As Boolean, _
      ByVal Target As Graphics, _
      ByVal PrintFont As Font)


        Dim x As Single, y As Single

        If DrawRectangle Then
            Target.DrawRectangle(_GridPen, BoundingRect)
        End If

        '\\ Set the text alignment
        If HorizontalAlignment = 
              CellTextHorizontalAlignment.LeftAlign Then
            _Textlayout.Alignment = StringAlignment.Near
        ElseIf HorizontalAlignment = 
                   CellTextHorizontalAlignment.RightAlign _
        Then
             _Textlayout.Alignment = StringAlignment.Far
        Else
             _Textlayout.Alignment = StringAlignment.Center
        End If

        Dim BoundingRectF As New RectangleF(BoundingRect.X + _CellGutter, _ 
                                BoundingRect.Y + _CellGutter,  _ 
                                BoundingRect.Width - (2 * _CellGutter),  _ 
                                BoundingRect.Height - (2 * _CellGutter))

        Target.DrawString(s, PrintFont, System.Drawing.Brushes.Black, _ 
                          BoundingRectF, _Textlayout)

End Function

There are two class level variables that are used to define how a cell is drawn: CellGutter specifies a margin (in pixels) between the edge of the string bounding box and the GridPen is the pen used to draw the box.

Now You Can Draw a Box, You Can Draw a Row

There are two types of rows you need to print in order to print a grid: a row of column headers and a row of data. This class always starts each new page with a row of column headers.

VB
Private Sub PrintGridHeaderLine(ByVal e As _
          System.Drawing.Printing.PrintPageEventArgs)

    Dim Top As Double = _PageContentRectangle.Top
    Dim Bottom As Double = Top + _Rowheight + (2 * _CellGutter)

    Top = RoundTo(Top, 2)
    Bottom = RoundTo(Bottom, 2)

    Dim nColumn As Integer

    For nColumn = 0 To GridColumnCount() - 1
        Dim rcCell As New Rectangle(_ColumnBounds(nColumn).Left, Top, _
                             _ColumnBounds(nColumn).Width, Bottom - Top)
        Call DrawCellString(GetColumnHeadingText(nColumn),  _
                            CellTextHorizontalAlignment.CentreAlign, _
                            CellTextVerticalAlignment.MiddleAlign, _
                            rcCell, True, e.Graphics, _PrintFont)
    Next
End Sub

This is fairly straightforward. The only tricky bit is getting the title of the column from the datagrid which is done thus:

VB
Private Function GetColumnHeadingText(ByVal Column As Integer) As String

    If TypeOf _DataGrid.DataSource Is DataTable Then
        Return CType(_DataGrid.DataSource, _
                    DataTable).Columns(Column).ToString
    ElseIf TypeOf _DataGrid.DataSource Is DataSet Then
        Return CType(_DataGrid.DataSource, DataSet).Tables( _
                  _DataGrid.DataMember).Columns(Column).ToString
    ElseIf TypeOf _DataGrid.DataSource Is DataView Then
        Return CType(_DataGrid.DataSource, _
                DataView).Table.Columns(Column).ToString
    Else
        'TODO : Get the column caption....
    End If
End Function

And to print a row of data, we do the same thing but with the row content of the current row:

VB
Private Sub PrintGridLine(ByVal e As _
         System.Drawing.Printing.PrintPageEventArgs, _ 
              ByVal RowNumber As Integer)

        Dim RowFromTop As Integer = RowNumber + 1 - _CurrentPrintGridLine
        Dim Top As Double = _PageContentRectangle.Top + (RowFromTop * _
                                         ((_CellGutter * 2) + _Rowheight))
        Dim Bottom As Double = Top + _Rowheight + (2 * _CellGutter)

        Top = RoundTo(Top, 2)
        Bottom = RoundTo(Bottom, 2)

        Dim Items() As Object

            If TypeOf _DataGrid.DataSource Is DataTable Then
                Items = CType(_DataGrid.DataSource, _
                        System.Data.DataTable).DefaultView.Item(_
                                         RowNumber - 1).Row.ItemArray
            ElseIf TypeOf _DataGrid.DataSource Is DataSet Then
                Items = CType(_DataGrid.DataSource, _
                              System.Data.DataSet).Tables(     _
                                _DataGrid.DataMember).DefaultView.Item(_
                                             RowNumber - 1).Row.ItemArray
            ElseIf TypeOf _DataGrid.DataSource Is DataView Then
                Items = CType(_DataGrid.DataSource, _
                         System.Data.DataView).Table.DefaultView.Item( _
                                             RowNumber - 1).Row.ItemArray
            Else
                'TODO : Get the content for the current row from the data 
                '       source ....
            End If

            Dim nColumn As Integer
            For nColumn = 0 To Items.Length - 1
                Dim rcCell As New Rectangle(_ColumnBounds(nColumn).Left, Top,_
                       _ColumnBounds(nColumn).Width, Bottom - Top)
                Call DrawCellString(Items(nColumn).ToString, _ 
                               CellTextHorizontalAlignment.CentreAlign, _ 
                               CellTextVerticalAlignment.MiddleAlign,  _ 
                               rcCell, True, e.Graphics, _PrintFont)
            Next
End Sub

Fitting It All Onto a Page

Image 3

The printed page is divided into three areas: the header, the body and the footer. There is also an InterSectionSpacing property that specifies a gap to use between the sections. These are all expressed as percentages of the page height and setting any of them to zero will omit that section.

The body section is worked out from what remains of the page height when the header, footer and inter section spacing have been added up and this is used to calculate how many rows can possibly fit on each page:

VB
Private Function RowsPerPage(ByVal GridLineFont As Font, _
                            ByVal e As Graphics) As Integer

    Return (_PageContentRectangle.Height / _
            ((_CellGutter * 2) + _Rowheight)) - 2

End Function

Settings that Change the Look of the Grid

Image 4

The color and width of the lines around the sections can be set with the HeaderPen, FooterPen and GridPen properties respectively.

Image 5

The font to use for each of the sections can be set with the HeaderFont, FooterFont and PrintFont properties respectively.

Using the Class to Preview and Print a DataGrid

If you have a datagrid on a form that you want to preview and optionally print using this class, you can add a System.Windows.Forms.PrintPreviewDialog to your form and a Print menu and add the following code to it:

VB
Private GridPrinter As DataGridPrinter

Private Sub MenuItem_File_print_Click(ByVal sender As Object, _
                               ByVal e As System.EventArgs) _
                               Handles MenuItem_File_print.Click

    If GridPrinter Is Nothing Then
        GridPrinter = New DataGridPrinter(Me.DataGrid1)
    End If

    With GridPrinter
        .HeaderText = "Some text"
        .HeaderHeightPercent = 10
        .FooterHeightPercent = 5
        .InterSectionSpacingPercent = 2
        '\\ Set any other properties to
        'affect the look of the grid...
    End With

    With Me.PrintPreviewDialog1
        .Document = GridPrinter.PrintDocument
        If .ShowDialog = DialogResult.OK Then
            GridPrinter.Print()
        End If
    End With
End Sub

If you wish to use a newer DataGridView control instead of a DataGrid control, there is a constructor that takes that as a parameter instead. Everything else will work the same way.

History

  • 2006-04-06: Created
  • 2016-01-24: Added constructor/code to use a DataGridView class as the grid data source

License

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


Written By
Software Developer
Ireland Ireland
C# / SQL Server developer
Microsoft MVP (Azure) 2017
Microsoft MVP (Visual Basic) 2006, 2007

Comments and Discussions

 
GeneralRe: Getting Header Text Pin
Duncan Edwards Jones14-Oct-05 0:40
professionalDuncan Edwards Jones14-Oct-05 0:40 
GeneralAutoSize font Pin
yorglaa13-Sep-05 1:36
yorglaa13-Sep-05 1:36 
GeneralRe: AutoSize font Pin
Duncan Edwards Jones13-Sep-05 7:35
professionalDuncan Edwards Jones13-Sep-05 7:35 
GeneralQuestions. Pin
charliewhit10-Sep-05 7:31
charliewhit10-Sep-05 7:31 
GeneralRe: Questions. Pin
Duncan Edwards Jones11-Sep-05 9:48
professionalDuncan Edwards Jones11-Sep-05 9:48 
GeneralRe: Questions. Pin
Duncan Edwards Jones13-Sep-05 7:27
professionalDuncan Edwards Jones13-Sep-05 7:27 
GeneralNew version uploaded Pin
Duncan Edwards Jones7-Sep-05 11:19
professionalDuncan Edwards Jones7-Sep-05 11:19 
GeneralIndex was out of range. Pin
charliewhit6-Sep-05 16:12
charliewhit6-Sep-05 16:12 
Iam trying to use your print grid control to print a grid that is bound to a dataview and the grid has 16 columns but 4 are not being displayed and iam also using data styles, I get "Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index." it looks like it happens on the 13th column. An ideas?
---------------------------------------
' Bind the data grid to the table.
grdCostDetail.DataSource = dvQICalc

' Restyle the datagrid.
With grdCostDetail
.TableStyles.Clear()
.PreferredColumnWidth = 150
.CaptionFont = New Font("Tahoma", 10.0!, FontStyle.Bold)
.CaptionVisible = True
.ColumnHeadersVisible = True
.CaptionText = dvQICalc.Count.ToString & " Bill of Material and Routing Steps"
.BackgroundColor = Color.DarkGray
.CaptionBackColor = Color.DarkKhaki
.CaptionForeColor = Color.Black
.ParentRowsBackColor = Color.LightGray
.ParentRowsForeColor = Color.Black
End With

' Restyle the datagrid tables.
' Create a new table style grid to do the restyling in.
Dim grdTableStyle As New DataGridTableStyle
With grdTableStyle
' Set the member name.
.MappingName = "QICalc"
' Set the alternating back color property.
.AlternatingBackColor = Color.WhiteSmoke
.BackColor = Color.Gainsboro
.ForeColor = Color.Black
.GridLineColor = Color.Silver
.LinkColor = Color.DarkSlateBlue
.SelectionBackColor = Color.DarkSlateGray
.SelectionForeColor = Color.White
.RowHeaderWidth = 10
.RowHeadersVisible = False
End With

' Restyle the datagrid columns.
' Restyle 1st column.
Dim grdTextColumn As New DataGridTextBoxColumn
grdTextColumn.MappingName = "PrintMethod"
grdTextColumn.HeaderText = "PM"
grdTextColumn.Width = 25
grdTextColumn.ReadOnly = True
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 2nd column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "StepSeq"
grdTextColumn.HeaderText = "Step"
grdTextColumn.Width = 35
grdTextColumn.ReadOnly = True
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 3rd column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "CostName"
grdTextColumn.HeaderText = "Step Name"
grdTextColumn.Width = 318
grdTextColumn.ReadOnly = True
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 4th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "Laborers"
grdTextColumn.HeaderText = "Lbrs"
grdTextColumn.Width = 30
grdTextColumn.Format = ("###.##")
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 5th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "DirCostPerPiece"
grdTextColumn.HeaderText = "Dir$/Piece"
grdTextColumn.Width = 63
grdTextColumn.Format = ("###.####")
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 6th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "OvhCostPerPiece"
grdTextColumn.HeaderText = "Ovh$/Piece"
grdTextColumn.Width = 68
grdTextColumn.Format = ("###.####")
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 7th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "MatCostPerPiece"
grdTextColumn.HeaderText = "Mat$/Piece"
grdTextColumn.Width = 66
grdTextColumn.Format = ("###.####")
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 8th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "TotCostPerPiece"
grdTextColumn.HeaderText = "$/Step"
grdTextColumn.Width = 55
grdTextColumn.Format = ("###.####")
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 9th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "CostPercentPerStep"
grdTextColumn.HeaderText = "%/$s"
grdTextColumn.Width = 50
grdTextColumn.Format = ("P1")
grdTextColumn.Alignment = HorizontalAlignment.Right
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 10th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "TimeString"
grdTextColumn.HeaderText = "Hrs/Step"
grdTextColumn.Width = 66
'grdTextColumn.Format = ("##,###.00")
grdTextColumn.Alignment = HorizontalAlignment.Right
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 11th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "TimePercentPerStep"
grdTextColumn.HeaderText = "%/Hrs"
grdTextColumn.Width = 55
grdTextColumn.Format = ("P1")
grdTextColumn.Alignment = HorizontalAlignment.Right
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)
' Restyle 12th column.
grdTextColumn = New DataGridTextBoxColumn
grdTextColumn.MappingName = "Modified"
grdTextColumn.HeaderText = "M"
grdTextColumn.Width = 16
grdTextColumn.ReadOnly = False
grdTableStyle.GridColumnStyles.Add(grdTextColumn)

' Attach the Table Style to the datagrid.
grdCostDetail.TableStyles.Add(grdTableStyle)
--------------------
Private Sub MenuItemFilePrint_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles MenuItemFilePrint.Click
If GridPrinter Is Nothing Then
GridPrinter = New DataGridPrinter(Me.grdCostDetail)
End If
With GridPrinter
.HeaderText = "Bill of Materials and Routing Steps" 'Me.TextBox1.Text
.HeaderHeightPercent = 4 'Me.NumericUpDown_HeaderHeightPercentage.Value
.FooterHeightPercent = 3 'Me.NumericUpDown_FooterHeightPercent.Value
.InterSectionSpacingPercent = 2 'Me.NumericUpDown_InterSectionSpacingPercent.Value
'.HeaderPen = New Pen(CType(Me.ComboBox_ColourHeaderLine.SelectedItem, System.Drawing.Color))
'.FooterPen = New Pen(CType(Me.ComboBox_ColourFooterLine.SelectedItem, System.Drawing.Color))
'.GridPen = New Pen(CType(Me.ComboBox_ColourBodyline.SelectedItem, System.Drawing.Color))
End With
With Me.PrintPreviewDialog1
.Document = GridPrinter.PrintDocument
If .ShowDialog = DialogResult.OK Then
GridPrinter.Print()
End If
End With
End Sub
---------------------------------
Thanks, Charlie
GeneralRe: Index was out of range. Pin
Duncan Edwards Jones6-Sep-05 21:07
professionalDuncan Edwards Jones6-Sep-05 21:07 
GeneralRe: Index was out of range. Pin
Duncan Edwards Jones13-Sep-05 10:08
professionalDuncan Edwards Jones13-Sep-05 10:08 
GeneralRe: Index was out of range. Pin
Duncan Edwards Jones8-Apr-06 5:49
professionalDuncan Edwards Jones8-Apr-06 5:49 
GeneralRe: Index was out of range. Pin
Juan Salvador Hernandez23-Jan-07 9:04
Juan Salvador Hernandez23-Jan-07 9:04 
GeneralRe: Index was out of range. Pin
edangallaca26-Mar-08 12:57
edangallaca26-Mar-08 12:57 
GeneralFunciona para Visual Web Developer 2005 Pin
wilistmo76630-Aug-05 4:34
wilistmo76630-Aug-05 4:34 
GeneralRe: That work with Visual Web Developer 2005 Beta 2 Pin
wilistmo76630-Aug-05 4:39
wilistmo76630-Aug-05 4:39 
GeneralRe: That work with Visual Web Developer 2005 Beta 2 Pin
Duncan Edwards Jones30-Aug-05 5:26
professionalDuncan Edwards Jones30-Aug-05 5:26 
GeneralFooter Printing (Page "n" of "TotalPages") Pin
georani22-Aug-05 2:00
georani22-Aug-05 2:00 
GeneralRe: Footer Printing (Page "n" of "TotalPages") Pin
Duncan Edwards Jones22-Aug-05 2:39
professionalDuncan Edwards Jones22-Aug-05 2:39 
GeneralUseful code to test with this DataGrid (Suggestion) Pin
georani22-Aug-05 1:56
georani22-Aug-05 1:56 
GeneralWhen Printing Boolean Values (Suggestion) Pin
georani22-Aug-05 1:48
georani22-Aug-05 1:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.