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.
To Draw a Grid, First Draw a Square
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.
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
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.
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:
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
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:
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
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
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:
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
The color and width of the lines around the sections can be set with the HeaderPen
, FooterPen
and GridPen
properties respectively.
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:
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
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