Click here to Skip to main content
Click here to Skip to main content

Another DataGridView Printer

By , 24 Dec 2012
 

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.

//

// 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:

//

// 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:

//

// 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

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)

About the Author

aureolin
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionCannot Change RowHeight to CellHeight @ runtimememberE P Sharp21 May '13 - 14:44 
Absolutely love this software; but I cannot change RowHeight to CellHeight at run time; Have tried:
 
  printer = new DGVPrinter();
  printer.SubTitleFormatFlags = StringFormatFlags.LineLimit | StringFormatFlags.NoClip;
  printer.PageNumbers 	    = true;
  printer.PageNumberInHeader  = false;
  printer.PrintColumnHeaders  = true;
  printer.HeaderCellAlignment = StringAlignment.Near;
  printer.FooterSpacing       = 15;
 
  printer.RowHeight = printer.RowHeightSetting.CellHeight;
  printer.RowHeight = RowHeightSetting.CellHeight;
  printer.RowHeight = printer.CellHeight;
 
I have tried even more combinations but all yield compile errors. I have tried Google. As a last resort, I created DGVPrinter.dll and DGVPrinter2.dll and changed the default setting for RowHeight but it sure would be nice to only have to use one DLL.
 
Just a simple example of `printer.` for RowHeight ??
 
If anyone is in a really generous mood, a simple example of `printer.` for printing in the center of the page ??
 
Thank you in advance for your time and consideration.
 
Regards
 
Ed Sharp
 
ed-sharp@usa.net
AnswerRe: Cannot Change RowHeight to CellHeight @ runtimememberaureolin12hrs 10mins ago 
Sorry for the delayed reply. I think what you want is:
 
print.RowHeight = DGVPrinter.RowHeightSetting.CellHeight;
 
Let me know if this helps ...
 
Steve G.
QuestionIncorrect rowheightsmemberRon Des11 Apr '13 - 3:03 
Can anyone see my entry dated the 27/12/2012. I have not seen any response to that entry.
regards Ron.
AnswerRe: Incorrect rowheightsmemberaureolin11 Apr '13 - 10:44 
I had, and had thought I'd emailed you about it - however, I can't find the outgoing email. Perhaps my memory isn't what it used to be? Anyway, I'm on vacation at the moment. I'll look into this when I return. Thanks for reminding me about this one!
 
Steve G.
QuestionCell Padding seems to be ignored [modified]memberAHSTech10 Apr '13 - 5:31 
Hi Steve,
 
I have a DGV that for some columns containing numeric data I have the defaultcellstyle.alignment set to MiddleRight and the defaultcellstyle.padding set to e.g. 0,0,10,0 (padding to right of value only). This appears to cause DGVPrinter to draw the right border for that column nearer the cell value rather than drawing the right border in its correct place taking into account the padding applied to the cell value.
 
e.g. given three cells of which the middle one is the right-justified padded one:
 
Should be drawn as: | xxxx| 1234 |xxxx
is in fact drawn as: | xxxx| 1234| |xxxx
 
Any ideas?
 
Cheers
 
Derek

modified 10 Apr '13 - 13:56.

AnswerRe: Cell Padding seems to be ignoredmemberaureolin10 Apr '13 - 11:23 
That sounds very much like a bug. I'm on vacation and will take a look at things when I return. Thanks for reporting this!
 
Steve G.
QuestionTHANK YOU...!membersam7one6 Apr '13 - 1:19 
Thank you very much for such a SIMPLE and EASY article for printing GRIDVIEW....
I was searching for this very desperately, but all I found very complex codes, I used them, but there were some part of code snippet unexplained, making it hard to use.
 
But your code was very simple to implement...THANKS A LOT!
QuestionThanks!memberMember 16494882 Apr '13 - 4:44 
Just wanted to drop a few lines to thank the author for this excellent piece of code! It is well thought out and easy to implement. Again, many many thanks!
GeneralMy vote of 5membercesarms8915 Mar '13 - 5:24 
It's great works excellent and too easy to use Smile | :)
QuestionQuestion about overriding cell printing. Please help :)memberFamco26 Feb '13 - 4:49 
First, Let me say that I'm sure that this is the best DataGridView Printer ever made.
I just need some help concerning drawing text inside headers with a 270 angle. Apparently I'm not able to subscribe to the OwnerDraw event. What does this mean? print.OwnerDraw += new CellOwnerDrawEventHandler(print_OwnerDraw). Is there anything missing?
AnswerRe: Question about overriding cell printing. Please help :)memberaureolin1 Mar '13 - 18:33 
First, my apologies for not responding sooner - I've just discovered that some of the post emails from CodeProject are going into my junk folder!
 
To answer your question, the statement you list there registers a handler for the OwnerDraw event. The argument "print_OwnerDraw" is actually the name of a procedure that will get called when the OwnerDraw event is triggered. You write this routine. The code in this routine is your opportunity to do what you need to to handle the OwnerDraw event. I'd strongly suggest you search the internet for help on handling events - there's lots of examples out there to help you wire up the event handler properly.
 
Good Luck,
Steve G.
SuggestionRow header cell printing.memberRon Des9 Feb '13 - 10:06 
Currently when the row header is printed the Row Style is used as in
 
//-----------------------------------------------------------------
// Print Row background
//-----------------------------------------------------------------
 
// get current row style, start with header style
DataGridViewCellStyle rowstyle = row.InheritedStyle.Clone();
 

Should rowstyle not be defined as
 
DataGridViewCellStyle rowstyle = row.HeaderCell.InheritedStyle.Clone();
 
The row headers will then have the same look as the datagridcview.
 
regards ron
GeneralRe: Row header cell printing.memberaureolin11 Feb '13 - 8:13 
Good catch, but actually, the change needs to be made in the code just below there - where the header cell is printed.
 
Thanks, I've added this to the list of fixes for the next release.
 
Steve G.
Suggestion3 suggestion.memberqakmak5 Feb '13 - 17:06 
I have 3 suggestion.
1. can u add some custom row in a doc end? for display the total price, percentage as well as some tips. before i think add this to DataGridView and print, but when print is finish , i should clear that row. if i can add multiple DataGridView, and you combine this ? , it's so easy for me.
 
2.is there any way can let the column width automatically populated? if i'm use
DGVPrinter.ColumnWidthSetting.CellWidth
or
DGVPrinter.ColumnWidthSetting.Porportional
Its width may exceed the page, if i'm use
ColumnWidthSetting.DataWidth
It will be remaining lot of space. can u just let It automatically calculates the width to fit the page? (Beyond the automatic line feed)?
 
3.if i'm print , i only can print vertical mode on A4. can i choise the print mode like horizontal?
 
very thanks.
GeneralExcellent Piece of WorkmemberRobert Styma10 Jan '13 - 4:14 
Excellent piece of work. It only takes a small change to make it compatible back to Dot Net 2.0. Online conversion from C# to VB.net also worked cleanly.
GeneralMy vote of 5memberMember 39806578 Jan '13 - 14:01 
Does anyone have a VB.NET conversion?
QuestionVB.net and using with printpreviewcontrolmemberMember 240493627 Jan '13 - 0:39 
Hi,
I need to make my own print preview page to use a Ribbon Control like in Paint and show the print preview not in a new dialog form.
 
Here my code:
Private Sub PT_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PT.PrintPage
 
            Dim printer = New DGVPrinter()
            Dim prflex As DataGridView
 
            prflex = XpTabControl1.SelectedTab.Controls(0)
            printer.RowHeight = DGVPrinter.RowHeightSetting.CellHeight
            printer.EmbeddedPrint(prflex, e.Graphics, New Rectangle(10, 10, prflex.Width, prflex.Height))
 
        End Sub
After this I set
PrintPreviewControl1.Document = PT
I have problems to translate the Embedded Printing Process for Multipage Printing C# code to VB.net, anybody just used this feature in VB?
Here a link to my print preview
https://dl.dropbox.com/u/39631349/preview_Another%20DataGridView%20Printer.png[^]
 
Thanks, Thomas
QuestionIncorrect rowheights when datagridview wider than a printed page. [modified]memberRon Des27 Dec '12 - 7:12 
When the datagridview is wider than a printed page and the RowHeight setting is CellHeight then printing is fine irrespective of 'porportional' setting.
But when RowHeight is set to DataHeight then the height of the rows seems to depend upon the largest text string within that row even though the text is not wrapped.
regards ron.
 
further to the problem above
the problem seems to be the connection between AdjustPageSets and RecalcRowHeights. the routine AdjustPagesets is entered with a pageset which calls RecalcRowHeights with the number of columns within that pageset. On further pagesets RecalcRowHeights starts off looking at columns within the first pageset and because the column widths are different the heights of the rows are adjusted even though data in cells is not wrapped.
 
In the routine calccellsize after the else statement I put in a mod because a checkboxcell returns an invalid height when g.MeasureString(String,Font,Size,format) is used, at least it does on my system.
 
else
{
if (cell is DataGridViewColumnHeaderCell)
datasize = g.MeasureString(cell.OwningColumn.HeaderText.ToString(), cellstyle.Font,
new SizeF(basewidth, maxPages), format);
else
{
if ("DataGridViewCheckBoxCell" == dgv.Columns[cell.ColumnIndex].CellType.Name)
datasize = g.MeasureString(cell.EditedFormattedValue.ToString(), cellstyle.Font);
else
datasize = g.MeasureString(cell.EditedFormattedValue.ToString(), cellstyle.Font,
new SizeF(basewidth, maxPages), format);
}
// if we have excessively large cell, limit it to one page width
if (printWidth < datasize.Width)
datasize = g.MeasureString(cell.FormattedValue.ToString(), cellstyle.Font,
new SizeF(pageWidth - cellstyle.Padding.Left - cellstyle.Padding.Right, maxPages),
format);
}
 
happy new year readers
 
further to the problem between AdgustPageSets and RecalcRowHeights i put in a mod
int prevColCount = 0;
for (i = 0; i < pagesets.Count; i++)
{
AdjustPageSets(g, pagesets[i], prevColCount);
prevColCount += pagesets[i].colwidths.Count;
}
and within AdjustPageSets
private void AdjustPageSets(Graphics g, PageDef pageset, int prevcolCount)
{
.
.
RecalcRowHeights(g, i + prevcolCount, pageset.colwidths[i]);
.
}
this rough and ready mod fixed the problem of rowheights along with the calccellsize.
regards Ron

modified 2 Jan '13 - 9:40.

BugIt does not print the header column of dgv when the first dgv column property visible is set to FALSEmemberMember 808473623 Dec '12 - 8:45 
I have a dgv that contains some ID's that I do not want to be showed, so in the dgv properties, where you edit the columns, the first column's ID property "visible" I set it to "false", so when I print, the header is missing...
 
Java | [Coffee] to fix that I moved to the last column this invisible column ID and then the printer could show the header of dgv
 
Thanks. Big Grin | :-D
GeneralRe: It does not print the header column of dgv when the first dgv column property visible is set to FALSE [modified]memberaureolin24 Dec '12 - 6:14 
I'm trying to recreate the issue you found I'm not sure I'm following you. Are you saying that when you hid the first column, that all the row headers were not displayed?
 
-- Edit --
After a bit of poking about, I've found the issue. It will be fixed in the next update. Thanks for reporting this!
-- Edit --
 

Thanks,
Steve G.

modified 24 Dec '12 - 13:43.

BugRe: It does not print the header column of dgv when the first dgv column property visible is set to FALSEmemberMember 808473624 Dec '12 - 7:38 
Yes exactly, when I hide the first column in the dgv (In the designer, when you edit the dgv properties, there is one called VISIBELE -{TRUE, FALSE})
 
If I set the property visible to false to the first column in dgv, when printing the header of columns iof h()s not appearing
GeneralMy vote of 4memberNIBERIUM20 Dec '12 - 3:56 
Good work! But code formating...
GeneralRe: My vote of 4memberaureolin20 Dec '12 - 5:39 
My code is perfectly formatted ... for me. Laugh | :laugh:
 
Seriously, thanks for the good vote.
QuestionProblemmemberNIBERIUM20 Dec '12 - 2:52 
Throws exceprion "Argument out of range..." in:
calccellsize(g, col.HeaderCell, i, headercolstyle, usewidth, columnheadercellformat);
call, when trying get property cell.EditedFormattedValue.
 
MSDN, says:
Exception	                         Condition
ArgumentOutOfRangeException         The row containing the cell is a shared row.
                                    -or-
                                    The cell is a column header cell.
Why, then, this is not happening all the time, but only from a specific table?
AnswerRe: ProblemmemberNIBERIUM20 Dec '12 - 3:55 
The problem was solved in this way:
// measure the data for each column, keep widths and biggest height
if (cell is DataGridViewColumnHeaderCell)
    datasize = g.MeasureString(cell.OwningColumn.HeaderText.ToString(),cellstyle.Font,
        new SizeF(basewidth, maxPages), format);
else
    datasize = g.MeasureString(cell.EditedFormattedValue.ToString(), cellstyle.Font,
        new SizeF(basewidth, maxPages), format);
And so in all the places where you are trying to get EditedFormattedValue.
 

Also further found another bug:
// determine checked or notchecked
CheckBoxState state = (checkboxcell.Value == null || (Boolean)checkboxcell.Value == false) ?
                CheckBoxState.UncheckedNormal : CheckBoxState.CheckedNormal;
Skipped check for DBNull. After change:
  
// determine checked or notchecked
CheckBoxState state = (checkboxcell.Value == null || checkboxcell.Value == DBNull.Value || (Boolean)checkboxcell.Value == false) ?
                CheckBoxState.UncheckedNormal : CheckBoxState.CheckedNormal;

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 24 Dec 2012
Article Copyright 2007 by aureolin
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid