Click here to Skip to main content
15,906,333 members
Articles / Programming Languages / Visual Basic

Another DataGridView Printer

Rate me:
Please Sign up or sign in to vote.
4.86/5 (186 votes)
6 Feb 2014CPOL5 min read 4.3M   55.8K   412   984
DataGridView printing encapsulated in a stand-alone object. Very easy to use! Updated to allow printing columns wider than one page.

Introduction

I went looking for a class to do printing from a DataGridView, and none of them did all that I was looking for. I needed to print all pages, some pages, or the current selection; and I needed to not have objects, controls, or code from the printer object sprinkled through the rest of my code - i.e., it needed to be completely self-contained. Nothing I found met all those requirements, so I ended up writing my own.

Using the Code

To use the DGVPrinter class, you have two options. First, you can simply add the DGVPrinter.cs source file to your project, or second you can place the DLL in your "Bin" directory and add a reference to the DGVPrinter.dll to your project's references. In either case, to use the DGVPrinter, you will only need to add a "using DGVPrinter" to your code file, and create an instance of the object.

C#
//

// The using block statement

//

using DGVPrinterHelper;
//

// The using block statement

//

imports DGVPrinterHelper;

For example, if you wanted to print a DataGridView when your user clicks a Print button on the toolbar, your code might look something like this:

C#
//

// Printing the DataGridView Control

// in response to a toolbar button press

//

private void printToolStripButton_Click(object sender, EventArgs e)

{

    DGVPrinter printer = new DGVPrinter();

    printer.Title = "DataGridView Report";

    printer.SubTitle = "An Easy to Use DataGridView Printing Object";

    printer.SubTitleFormatFlags = StringFormatFlags.LineLimit | 

                                  StringFormatFlags.NoClip;

    printer.PageNumbers = true;

    printer.PageNumberInHeader = false;

    printer.PorportionalColumns = true;

    printer.HeaderCellAlignment = StringAlignment.Near;

    printer.Footer = "Your Company Name Here";

    printer.FooterSpacing = 15;



    printer.PrintDataGridView(datagridviewControl);

}
//

// Printing the DataGridView Control

// in response to a toolbar button press

//

Public Class Form1 

    Private Sub btnPrintGridview_Click(ByVal sender As System.Object, _

    ByVal e As System.EventArgs) Handles btnPrintGridView.Click

        Dim Printer = New DGVPrinter

        Printer.Title = "DataGridView Report"

        Printer.SubTitle = "An Easy to Use DataGridView Printing Object"

        Printer.SubTitleFormatFlags = StringFormatFlags.LineLimit Or _

                    StringFormatFlags.NoClip

        Printer.PageNumbers = True

        Printer.PageNumberInHeader = False

        Printer.PorportionalColumns = True

        Printer.HeaderCellAlignment = StringAlignment.Near

        Printer.Footer = "Your Company Name Here"

        Printer.FooterSpacing = 15

        Printer.PrintDataGridView(Me.DataGridView1)

    End Sub

The basic interface used here provides a neat, one-stop-shop for printing a DataGridView. But, what if you want to have more control over the printing process? Say you'd like to save your users' print preferences or provide a default printer? To help you with this, DGVPrinter provides a more complex interface. Here's an example where the calling program provides overrides to the PrinterSettings and the DefaultPageSettings:

C#
//

// Printing the DataGridView Control

// in response to a toolbar button press – 

// the myprintsettings and mypagesettings objects are objects used by the local

// program to save printer and page settings

//

private void printToolStripButton_Click(object sender, EventArgs e)

{

    DGVPrinter printer = new DGVPrinter();

    printer.Title = "DataGridView Report";

    printer.SubTitle = "An Easy to Use DataGridView Printing Object";

    printer.SubTitleFormatFlags = StringFormatFlags.LineLimit | 

        StringFormatFlags.NoClip;

    printer.PageNumbers = true;

    printer.PageNumberInHeader = false;

    printer.PorportionalColumns = true;

    printer.HeaderCellAlignment = StringAlignment.Near;

    printer.Footer = "Your Company Name Here";

    printer.FooterSpacing = 15;



    // use saved settings

    if (null != myprintsettings) 

        printer.PrintDocument.PrinterSettings = myprintsettings;

    if (null != mypagesettings)

        printer.PrintDocument.DefaultPageSettings = mypagesettings;



    if (DialogResult.OK == printer.DisplayPrintDialog())  // replace DisplayPrintDialog() 

                           // with your own print dialog

    {

        // save users' settings 

        myprintsettings = printer.PrinterSettings;

        mypagesettings = printer.PageSettings;



        // print without displaying the printdialog

        printer.PrintNoDisplay(datagridviewControl);

    }

}

DGVPrinter's various settings provide good control of all aspects of printing on the page. You can set the Title and Subtitles, add a footer, and control whether the page number prints in the header or footer. DGVPrinter supports Right-to-Left printing for non-Western languages and includes a drawing override for situations where a cell or column has onPaint overridden in the source DataGridView control. While the default styles for the printed DataGridView are taken from the source DataGridView control, DGVPrinter also provides many attributes that allow you to control the styling of almost every aspect of the printout.

History

  • Version 1.0 - Initial publication
  • Version 1.1 - Added footer handling, and allows the page number to print in the header or footer, and if it should print on the same line as the header or footer
  • Version 1.2 - Finally (I believe!), fixed the string/column alignment problems. Also prints cell background colors properly, respecting the alternating rows style
  • Version 1.3 - Added support for printing columns that contain images
  • Version 1.4 - Added support for printing directly to a provided Graphics object
  • Version 2.0 - Added support for printing images on the page
  • Version 3.0 - Breaking changes! Please read!
    1. Added support for cells/rows that span more than one page of depth. If a cell would run off the bottom of the page, the "KeepRowsTogether" property determines if a partial row is printed or a new page is started.
    2. Added support for Setting the styles for Row and Column Headers. The properties for setting Header cell styles changed names, and the return type of "PrintColumnHeaders" changed. This can cause your program to not compile/run!
    3. Added a default value so row headers will show up if they are supposed to be "visible"
    4. Added title and subtitle spacers. These will help give you control of the whitespace below the Title and Subtitle.
    5. Compiled version for VB and other language support
  • Version 3.1 - Fix cell background color printing
  • Version 3.2 - Fixes for Embedded Print function
  • Version 3.3 - Unlikely but possible breaking change
    1. Add Delegate to allow "Owner Drawing" of cells, including row and column headers
    2. Add better support for cell size, data size or proportional scaling of columns. The identifier 'StringHeight' has been changed to 'DataHeight' since the size of an image is now properly accounted for. This may break your code if you depend on this feature.
    3. Bug Fixes
  • Version 3.4 - Add support for Alternating Rows when ColumnStyles are overridden
  • Version 3.5 - More fixes for Alternating Rows ColumnStyles
  • Version 3.6 - Fix for Imbedded Image drawing; images now draw at original pixel sizes without scaling
  • Version 3.7 - Fix for large text wrapping
  • Version 4.0 - Fixes and lots of new functionality!
    1. Bug Fixes
      1. Font resizing problem found by Member 8539779
      2. Rows that spanned multiple pages did not properly respect Right-to-Left Language setting
    2. New Features - Many thanks to everyone who suggested a new function or feature!!
      1. Set background colors/shading for Title, Subtitle and Footer
      2. Set border style and colors for Title, Subtitle and Footer
      3. Hide Columns - Specify a list of columns that will not be printed
      4. Fixed Columns - Specify a list of columns that will be printed on every page (good for rows spanning multiple pages). Respects Right-to-Left language setting
      5. Break on Value Change - Specify a column to monitor. When the value in a cell of this columns changes, a page break is inserted.
      6. Checkbox column now prints as an actual graphic checkbox.
  • Version 4.1 
    1. Greatly expanded Embedded Print process, now supports multiple pages and exposes the BeginPrint and PrintPage events.
    2. Added log/tracing facility
    3. Bug Fix - thanks and kudos to B. Marik for patience and help while I tracked this one down. 
  • Version 4.2
    1. Bug Fix - Print no longer throws at error. Thanks to Member 8757586 for finding it for me!
  • Version 4.3
    1. Bug Fix - Improved handling of checkboxes, added support for tristate checkbox.
    2. Bug Fix - Column headers would not print if the first column was hidden
  • Version 4.4 (Thanks to Derek for finding and helping me find and fix these!)
    1. Bug Fix - handle cells with large amounts of data.
    2. Bug Fix - Footer printing over the bottom row of text in certian situations.

Gratitude, kudos and acknowledgements to everyone who suggested a feature or function or found a bug. DGV Printer just wouldn't be the same without everyone's ideas and input!

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
AnswerRe: Bug with text wrapping Pin
aureolin16-Jun-08 6:52
aureolin16-Jun-08 6:52 
QuestionRe: Bug with text wrapping Pin
Ralf Buschmann16-Jun-08 22:15
Ralf Buschmann16-Jun-08 22:15 
AnswerRe: Bug with text wrapping Pin
Ralf Buschmann16-Jun-08 23:27
Ralf Buschmann16-Jun-08 23:27 
GeneralRe: Bug with text wrapping Pin
aureolin17-Jun-08 16:09
aureolin17-Jun-08 16:09 
NewsBug Fix Pin
aureolin10-Apr-08 5:26
aureolin10-Apr-08 5:26 
NewsNew Property Pin
aureolin9-Apr-08 9:40
aureolin9-Apr-08 9:40 
GeneralRe: New Property Pin
Jab19579-Apr-08 17:36
Jab19579-Apr-08 17:36 
NewsSupport of frozen columns added [modified] Pin
Frédéric PREVOST6-Apr-08 23:06
Frédéric PREVOST6-Apr-08 23:06 
Hello,

First of all, it-s a great job. Thanks

I'm just coding some enhancements to answer to customer needs. The first enhancement i've done is the dynamic support for frozen columns.
All frozen columns defined in your DGV are automatically added on each page parts. Meanwhile, I adjust the number of frozen cols to be sure to add at least one non-frozen col on each part (else we can create a neverending print job Wink | ;-) )

<code>
/// <summary>
/// Scan all the rows and columns to be printed and calculate the
/// overall individual column width (based on largest column value),
/// the header sizes, and determine all the row heights.
/// </summary>
/// <param name="g">The graphics context for all measurements</param>
private void measureprintarea(Graphics g)
{
int i, j;
rowheights = new List<float>(rowstoprint.Count);
colwidths = new List<float>(colstoprint.Count);
headerHeight = 0;
footerHeight = 0;

// Store frozen cols to repeat them on each page's part
int frozenColsCount = 0;

// temp variables
DataGridViewColumn col;
DataGridViewRow row;

//-----------------------------------------------------------------
// measure the page headers and footers, including the grid column header cells
//-----------------------------------------------------------------

// measure the column headers
Font headerfont = dgv.ColumnHeadersDefaultCellStyle.Font;
if (null == headerfont)
headerfont = dgv.DefaultCellStyle.Font;

// set initial column sizes based on column titles
for (i = 0; i < colstoprint.Count; i++)
{
col = (DataGridViewColumn)colstoprint[i];

// measure the title for each column, keep widths and biggest height
SizeF size = g.MeasureString(col.HeaderText, headerfont);
colwidths.Add(size.Width);
colheaderheight = (colheaderheight < size.Height ? size.Height : colheaderheight);

if ( col.Frozen )
frozenColsCount++;
}

//-----------------------------------------------------------------
// measure the page number
//-----------------------------------------------------------------

if (pageno)
{
StringFormat format = managestringformat(pagenumberalignment, pagenumberformatflags,
dgv.DefaultCellStyle, overridetitleformat);
pagenumberHeight = (g.MeasureString("Page", pagenofont, printWidth, format)).Height;
}


//-----------------------------------------------------------------
// Calc height of header.
// Header height is height of page number, title, subtitle and height of column headers
//-----------------------------------------------------------------

// note that we dont count the page number height if it's not on a separate line
if (pagenumberontop && !pagenumberonseparateline)
{
headerHeight += pagenumberHeight;
}

if (!String.IsNullOrEmpty(title))
{
StringFormat format = managestringformat(titlealignment, titleformatflags,
null, overridetitleformat);
headerHeight += (g.MeasureString(title, titlefont, printWidth, format)).Height;
}

if (!String.IsNullOrEmpty(subtitle))
{
StringFormat format = managestringformat(subtitlealignment, subtitleformatflags,
null, overridetitleformat);
headerHeight += (g.MeasureString(subtitle, subtitlefont, printWidth, format)).Height;
}

headerHeight += colheaderheight;

//-----------------------------------------------------------------
// measure the footer, if one is provided. Include the page number if we're printing
// it on the bottom
//-----------------------------------------------------------------

if (!String.IsNullOrEmpty(footer))
{
StringFormat format = managestringformat(footeralignment, footerformatflags,
null, overridefooterformat);
footerHeight += (g.MeasureString(footer, footerfont, printWidth, format)).Height;
}

// note we don't count the page number height if it's not on a separate line
if (!pagenumberontop && !pagenumberonseparateline)
{
footerHeight += pagenumberHeight;
}

footerHeight += footerspacing;

//-----------------------------------------------------------------
// measure the grid to be printed ... this gets us all the row heights
// and an accurate measure of column widths for the printed area
//-----------------------------------------------------------------

Font defaultfont = dgv.DefaultCellStyle.Font;
Font cellfont;

for (i = 0; i < rowstoprint.Count; i++)
{
row = (DataGridViewRow) rowstoprint[i];
rowheights.Add(0);

// add row headers if they're visible
if (dgv.RowHeadersVisible)
{
SizeF rhsize = g.MeasureString(row.HeaderCell.EditedFormattedValue.ToString(),
headerfont);
rowheaderwidth = (rowheaderwidth < rhsize.Width) ? rhsize.Width : rowheaderwidth;
}

// calculate widths for each column. We're looking for the largest width needed for
// all the rows of data.
for (j = 0; j < colstoprint.Count; j++)
{
col = (DataGridViewColumn) colstoprint[j];

if (row.Cells[col.Name].HasStyle && (null != row.Cells[col.Name].Style.Font))
cellfont = row.Cells[col.Name].Style.Font;
else
cellfont = defaultfont;

// get the raw size of the string.
SizeF size = g.MeasureString(row.Cells[col.Name].EditedFormattedValue.ToString(),
cellfont);

// Handle fixed size cells and > printwidth cells where the width of the
// data won't fit. (I.E. need to stretch the row down the page)
if ((0 < colwidthsoverride[j]) || (size.Width > printWidth))
{
// set column width
if (0 < colwidthsoverride[j])
colwidths[j] = colwidthsoverride[j];
else if (size.Width > printWidth)
colwidths[j] = printWidth;

// remeasure the string with the new limits and proper formatting for wrapping.
// Use an absurd height value so that we can get the real number of lines printed
int chars, lines;
StringFormat format = managestringformat(cellalignment, cellformatflags,
col.InheritedStyle, overridecellformat);
g.MeasureString(row.Cells[col.Name].EditedFormattedValue.ToString(),
cellfont, new SizeF(colwidths[j], 2147483647), format,
out chars, out lines);

// set row height
float tempheight = lines * size.Height;
rowheights[i] = (rowheights[i] < tempheight ? tempheight : rowheights[i]);
}
else
{
colwidths[j] = (colwidths[j] < size.Width ? size.Width : colwidths[j]);
rowheights[i] = (rowheights[i] < size.Height ? size.Height : rowheights[i]);
}
}
}

//-----------------------------------------------------------------
// Break the columns accross page sets. This is the key to printing
// where the total width is wider than one page.
//-----------------------------------------------------------------

// assume everything will fit on one page
pagesets = new List<PageDef>();
pagesets.Add(new PageDef(printmargins, colstoprint.Count));
int pset = 0;

// Account for row headers
pagesets[pset].coltotalwidth = rowheaderwidth;

// split columns into page sets
float columnwidth;
for (i = 0; i < colstoprint.Count; i++)
{
// get initial column width
columnwidth = (colwidthsoverride[i] >= 0)
? colwidthsoverride[i] : colwidths[i];

// See if the column width takes us off the page - Except for the
// first column. This will prevent printing an empty page!! Otherwise,
// columns longer than the page width are printed on their own page
if (printWidth < (pagesets[pset].coltotalwidth + columnwidth) && i != 0)
{
pagesets.Add(new PageDef(printmargins, colstoprint.Count));
pset++;

// Account for row headers
pagesets[pset].coltotalwidth = rowheaderwidth;

// Adding frozen columns
frozenColsCount = AddFrozenColsForPageSet( pagesets[pset], frozenColsCount, i );
}

// update page set definition
pagesets[pset].colstoprint.Add(colstoprint[i]);
pagesets[pset].colwidths.Add(colwidths[i]);
pagesets[pset].colwidthsoverride.Add(colwidthsoverride[i]);
pagesets[pset].coltotalwidth += columnwidth;
}

//-----------------------------------------------------------------
// Adjust column widths and table margins for each page
//-----------------------------------------------------------------
for (i=0; i<pagesets.Count; i++)
AdjustPageSets(g, pagesets[i]);
}

/// <summary>
/// Add frozen columns on every new page created.
/// We adjust nummber of frozen coluns to be sure to print at least on non-frozen one.
/// </summary>
/// <param name="pageset">new page set in which we add frozen columns</param>
/// <param name="frozenColsCount">number of defined frozen columns</param>
/// <param name="nextCol">first non frozen column to add to this page set</param>
/// <returns>corrected number of frozen columns</returns>
private int AddFrozenColsForPageSet( PageDef pageset, int frozenColsCount, int nextCol ) {

float totalWidth = 0;
float columnwidth;

if ( frozenColsCount >= nextCol ) {
frozenColsCount -= 2;
if ( frozenColsCount < 1 )
frozenColsCount = 1;
}

// compute resulting width after add of next column
totalWidth = ( colwidthsoverride[nextCol] >= 0 )
? colwidthsoverride[nextCol] : colwidths[nextCol];

for ( int i = 0; i < frozenColsCount; i++ ) {

// get initial column width
columnwidth = ( colwidthsoverride[i] >= 0 )
? colwidthsoverride[i] : colwidths[i];

if ( printWidth < ( totalWidth + columnwidth ) ) {
return frozenColsCount;
}

// update page set definition
pageset.colstoprint.Add( colstoprint[i] );
pageset.colwidths.Add( colwidths[i] );
pageset.colwidthsoverride.Add( colwidthsoverride[i] );
pageset.coltotalwidth += columnwidth;
totalWidth += columnwidth;
}
return frozenColsCount;
}

</code>

modified on Monday, April 7, 2008 5:13 AM
GeneralRe: Support of frozen columns added Pin
aureolin7-Apr-08 9:30
aureolin7-Apr-08 9:30 
QuestionHow to Print multi-subtitle? Pin
dragonren_tj2-Apr-08 16:11
dragonren_tj2-Apr-08 16:11 
GeneralRe: How to Print multi-subtitle? Pin
aureolin2-Apr-08 19:54
aureolin2-Apr-08 19:54 
GeneralRe: How to Print multi-subtitle? Pin
Goran _16-Nov-09 1:15
Goran _16-Nov-09 1:15 
GeneralRe: How to Print multi-subtitle? Pin
aureolin16-Nov-09 6:35
aureolin16-Nov-09 6:35 
GeneralIndex Outof range error Pin
Hitehs Patel1-Apr-08 2:57
Hitehs Patel1-Apr-08 2:57 
GeneralRe: Index Outof range error Pin
aureolin1-Apr-08 8:26
aureolin1-Apr-08 8:26 
GeneralRe: Index Outof range error Pin
Hitehs Patel1-Apr-08 18:26
Hitehs Patel1-Apr-08 18:26 
AnswerRe: Index Outof range error Pin
aureolin1-Apr-08 18:46
aureolin1-Apr-08 18:46 
GeneralRe: Index Outof range error Pin
Hitehs Patel1-Apr-08 20:31
Hitehs Patel1-Apr-08 20:31 
GeneralRe: Index Outof range error Pin
Hitehs Patel2-Apr-08 23:13
Hitehs Patel2-Apr-08 23:13 
GeneralRe: Index Outof range error Pin
aureolin3-Apr-08 5:10
aureolin3-Apr-08 5:10 
GeneralNice one, but I prefer use this Pin
nemopeti27-Mar-08 1:50
nemopeti27-Mar-08 1:50 
GeneralRe: Nice one, but I prefer use this Pin
aureolin27-Mar-08 5:10
aureolin27-Mar-08 5:10 
GeneralRe: Nice one, but I prefer use this Pin
nemopeti27-Mar-08 5:49
nemopeti27-Mar-08 5:49 
NewsUpdate - (again!) Wrapping within Fixed Width Columns Pin
aureolin26-Mar-08 10:17
aureolin26-Mar-08 10:17 
NewsUpdate - Printing wider than one page now supported! Pin
aureolin25-Mar-08 12:44
aureolin25-Mar-08 12:44 

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.