Download in C# and Visual Basic .NET
The standard WinForm DataGrid control that is included with the .NET Framework does not inheritantly
expose a method for printing its contents. Undoubtedly, there are several third party vendors that offer DataGrid components which include such
support. However, this article will demonstrate that with relatively little code you can implement basic print functionality in
a WinForm DataGrid.
To achieve this, we will create a custom DataGridPrintDocument class that is derived from the native
System.Drawing.Printing.PrintDocument class and override both its OnBeginPrint and OnPrintPage methods.
A simple WinForm with a DataGrid control, filled with data from the Customers table of
the Northwind database, will act as our test client.
Creating the DataGridPrintDocument class
DataGridPrintDocument.cs
First, we declare some class level variables that will be used in our print job logic and create a class constructor that will take a DataGrid object as a
parameter.
public class DataGridPrintDocument:PrintDocument
{
#region "Class Variables"
private DataGrid _DataGrid;
private int _DataRowCount;
private int _CurrentPrintRow;
private int _CurrentPrintCol;
private DataGridTableStyle _DataGridTableStyle;
private Font _ColumnHeaderFont;
private Font _RecordFont;
private bool _ResetPrintRow;
#endregion
public DataGridPrintDocument(DataGrid dataGrid)
{
_DataGrid=dataGrid;
}
|
Next, we override the OnBeginPrint method of the base PrintDocument class. This method is called only once, before the first page
of our document prints, and is an ideal place to initialize any class level variables that will be used later during the print process (e.g. setting fonts, getting the current DataGrid TableStyle, getting the number of rows to be printed, etc.)
Notice that we attempt to get the TableStyle of the current display member of our DataGrid. During the print process, we will use its GridColumnStyles collection to output the appropriate headers and correct width for each column.
If the DataGrid does not have a TableStyle for the current display member, we call a support function of our class, CreateGridTableStyle, to create a TableStyle based on the underlying DataTable of our DataGrid.
protected override void OnBeginPrint(PrintEventArgs e)
{
base.OnBeginPrint(e);
_CurrentPrintRow = 0;
_CurrentPrintCol = 0;
if (_ColumnHeaderFont == null)
_ColumnHeaderFont = _DataGrid.HeaderFont;
if (_RecordFont == null)
_RecordFont = _DataGrid.Font;
string CurrentDisplayMember=GetCurrentGridDisplayMember();
_DataGridTableStyle=_DataGrid.TableStyles[CurrentDisplayMember];
if (_DataGridTableStyle==null)
{
DataTable dt=new DataTable();
Type t=_DataGrid.DataSource.GetType();
if (t==typeof(DataSet))
dt=((DataSet)(_DataGrid.DataSource)).Tables[CurrentDisplayMember];
else if (t==typeof(DataView))
dt=((DataView)(_DataGrid.DataSource)).Table;
else
dt=(DataTable)(_DataGrid.DataSource);
_DataGridTableStyle=CreateGridTableStyle(dt);
}
_DataRowCount = _DataGrid.BindingContext[_DataGrid.DataSource, _DataGrid.DataMember].Count;
}
|
Lastly, we override the OnPrintPage method of the base PrintDocument class. This method
will be called for each page in our print job and will handle all the necessary logic for rendering the contents of our DataGrid. We first loop through the GridColumnStyles
collection of the current TableStyle and output the header of each column using the value in the HeaderText property. We then loop through the rows of the DataGrid
and write out the column values for each. Note that before writing out a new row to the page, we check to ensure its y location will not exceed the available space of the
MarginBounds.Bottom property value. Similarly, we check each column to ensure its x location will not exceed the available space of the
MarginBounds.Right property. If a new row or column will exceed the space on the current page, we break from the current loop and force a new page. Additionally, if there are more columns to print for the current row, we set the _ResetPrintRow value to false. This will
cause the next page to begin its output at the correct row and column from the previous page. Finally, we check if an additional page is required and set the HasMorePages property of the PrintPageEventArgs object to the appropriate boolean value.
protected override void OnPrintPage(PrintPageEventArgs e)
{
base.OnPrintPage(e);
int RowIndex;
int ColIndex;
float x = e.PageSettings.Margins.Left;
float y = e.PageSettings.Margins.Top;
Brush brush=new SolidBrush(Color.Black);
private const float HEADER_SPACING_Y = 15;
_ResetPrintRow = true;
for (ColIndex = _CurrentPrintCol; ColIndex < = _DataGridTableStyle.GridColumnStyles.Count-1; ColIndex++)
{
if ((x + _DataGridTableStyle.GridColumnStyles[ColIndex].Width) > = e.MarginBounds.Right)
{
_ResetPrintRow = false;
break;
}
else
{
string HeaderText=_DataGridTableStyle.GridColumnStyles[ColIndex].HeaderText;
e.Graphics.DrawString(HeaderText, _ColumnHeaderFont, brush, x, y);
x += _DataGridTableStyle.GridColumnStyles[ColIndex].Width;
}
}
int i=0;
for (RowIndex = _CurrentPrintRow; RowIndex < = _DataRowCount-1; RowIndex++)
{
x = e.PageSettings.Margins.Left;
y = e.PageSettings.Margins.Top + (HEADER_SPACING_Y + (i * _RecordFont.GetHeight(e.Graphics)));
if (y > = e.MarginBounds.Bottom)
break;
else
{
for (ColIndex = _CurrentPrintCol; ColIndex < = _DataGridTableStyle.GridColumnStyles.Count-1; ColIndex++)
{
if ((x + _DataGridTableStyle.GridColumnStyles[ColIndex].Width) > = e.MarginBounds.Right)
{
_ResetPrintRow = false;
break;
}
else
{
string CellText="";
if (_DataGrid[RowIndex, ColIndex] != DBNull.Value)
CellText=_DataGrid[RowIndex, ColIndex].ToString().Replace(Environment.NewLine," ");
e.Graphics.DrawString(CellText, _RecordFont, brush, x, y);
x += _DataGridTableStyle.GridColumnStyles[ColIndex].Width;
}
}
i += 1;
}
}
if (_ResetPrintRow==true)
{
_CurrentPrintRow = RowIndex;
_CurrentPrintCol = 0;
}
else
_CurrentPrintCol = ColIndex;
if (_CurrentPrintRow == _DataRowCount && _CurrentPrintCol == 0)
e.HasMorePages = false;
else
e.HasMorePages = true;
}
|
Using the DataGridPrintDocument
To test the custom DataGridPrintDocument class, we simply create an instance of it on our form and set it equal to the Document property of either
the PrintPreviewDialog or PrintDialog components. The print document will render using the settings of the one TableStyle defined in the DataGrid.
