Click here to Skip to main content
15,916,941 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

 
GeneralHi Pin
sraban18-Nov-09 22:16
sraban18-Nov-09 22:16 
GeneralReport footer Pin
Goran _16-Nov-09 1:28
Goran _16-Nov-09 1:28 
GeneralRe: Report footer Pin
aureolin16-Nov-09 6:36
aureolin16-Nov-09 6:36 
GeneralLogos and Watermarks Pin
aureolin6-Nov-09 13:38
aureolin6-Nov-09 13:38 
GeneralProblem in dll Pin
P200610-Oct-09 8:31
P200610-Oct-09 8:31 
GeneralRe: Problem in dll Pin
aureolin10-Oct-09 11:57
aureolin10-Oct-09 11:57 
GeneralRe: Problem in dll Pin
P200610-Oct-09 12:44
P200610-Oct-09 12:44 
AnswerRe: Problem in dll - VB CODE FOR YOU [modified] Pin
stixoffire24-Nov-09 6:38
stixoffire24-Nov-09 6:38 
Yes I would like to do the same with this because it is so much easier to manage and use in a dll. I group collections of controls that I really like into a DLL and use them in my applications - it makes it very simple to have a set of functions built into one dll rather than adding several files. Anyway - I translated to VB, it is at this moment untested but should work. Cheers

Imports System
Imports System.Text
Imports System.Collections
Imports System.Collections.Generic
Imports System.Drawing
Imports System.Drawing.Printing
Imports System.Data
Imports System.Globalization
Imports System.Windows.Forms

'[module:CLSCompliant(true)] 
Namespace DGVPrinterHelper
    'AllocationRequest 
    ''' <summary> 
    ''' Data Grid View Printer. Print functions for a datagridview, since MS 
    ''' didn't see fit to do it. 
    ''' </summary> 
    Class DGVPrinter
        '--------------------------------------------------------------------- 
        ' internal classes/structs 
        '--------------------------------------------------------------------- 

        ' handle wide-column printing - that is, lists of columns that extend 
        ' wider than one page width. Columns are broken up into "Page Sets" that 
        ' are printed one after another until all columns are printed. 
        Private Class PageDef
            Public Sub New(ByVal m As Margins, ByVal count As Integer)
                colstoprint = New List(Of Object)(count)
                colwidths = New List(Of Single)(count)
                colwidthsoverride = New List(Of Single)(count)
                coltotalwidth = 0
                margins = DirectCast(m.Clone(), Margins)
            End Sub

            Public colstoprint As IList
            Public colwidths As List(Of Single)
            Public colwidthsoverride As List(Of Single)
            Public coltotalwidth As Single
            Public margins As Margins
        End Class
        Private pagesets As IList(Of PageDef)
        Private currentpageset As Integer = 0

        ' class to hold settings for the PrintDialog presented to the user during 
        ' the print process 
        Public Class PrintDialogSettingsClass
            Public AllowSelection As Boolean = True
            Public AllowSomePages As Boolean = True
            Public AllowCurrentPage As Boolean = True
            Public AllowPrintToFile As Boolean = False
            Public ShowHelp As Boolean = True
            Public ShowNetwork As Boolean = True
            Public UseEXDialog As Boolean = True
        End Class


        '--------------------------------------------------------------------- 
        ' global variables 
        '--------------------------------------------------------------------- 
#Region "global variables"

        ' the data grid view we're printing 
        Private dgv As DataGridView = Nothing

        ' print document 
        Private printDoc As PrintDocument = Nothing

        ' print status items 
        Private rowstoprint As IList
        Private colstoprint As IList
        ' divided into pagesets for printing 
        Private lastrowprinted As Integer = -1
        Private fromPage As Integer = 0
        Private toPage As Integer = -1

        ' page formatting options 
        Private pageHeight As Integer = 0
        Private pageWidth As Integer = 0
        Private printWidth As Integer = 0
        Private rowheaderwidth As Single = 0
        Private CurrentPage As Integer = 0
        Private printRange As PrintRange

        ' calculated values 
        Private headerHeight As Single = 0
        Private footerHeight As Single = 0
        Private pagenumberHeight As Single = 0
        Private colheaderheight As Single = 0
        Private rowheights As List(Of Single)
        Private colwidths As List(Of Single)
#End Region

        '--------------------------------------------------------------------- 
        ' properties - settable by user 
        '--------------------------------------------------------------------- 
#Region "properties"

#Region "global properties"
        'public Form Owner 
        '{ get; set; } 
        ''' <summary> 
        ''' provide an override for the print preview dialog "owner" field 
        ''' Note: Changed style for VS2005 compatibility 
        ''' </summary> 
        Protected _Owner As Form = Nothing
        Public Property Owner() As Form
            Get
                Return _Owner
            End Get
            Set(ByVal value As Form)
                _Owner = value
            End Set
        End Property

        'public Double PrintPreviewZoom 
        '{ get; set; }

        ''' <summary> 
        ''' provide an override for the print preview zoom setting 
        ''' Note: Changed style for VS2005 compatibility 
        ''' </summary>
        Protected _PrintPreviewZoom As [Double] = 1.0R
        Public Property PrintPreviewZoom() As [Double]
            Get
                Return _PrintPreviewZoom
            End Get
            Set(ByVal value As [Double])
                _PrintPreviewZoom = value
            End Set
        End Property


        ''' <summary> 
        ''' expose printer settings to allow access to calling program 
        ''' </summary> 
        Public ReadOnly Property PrintSettings() As PrinterSettings
            Get
                Return printDoc.PrinterSettings
            End Get
        End Property

        ''' <summary> 
        ''' expose settings for the PrintDialog displayed to the user 
        ''' </summary> 
        Private m_printDialogSettings As New PrintDialogSettingsClass()
        Public ReadOnly Property PrintDialogSettings() As PrintDialogSettingsClass
            Get
                Return m_printDialogSettings
            End Get
        End Property

        ''' <summary> 
        ''' Set Printer Name 
        ''' </summary> 
        Private m_printerName As [String]
        Public Property PrinterName() As [String]
            Get
                Return m_printerName
            End Get
            Set(ByVal value As [String])
                m_printerName = value
            End Set
        End Property

        ''' <summary> 
        ''' Allow access to the underlying print document 
        ''' </summary> 
        Public Property printDocument() As PrintDocument
            Get
                Return printDoc
            End Get
            Set(ByVal value As PrintDocument)
                printDoc = value
            End Set
        End Property

        ''' <summary> 
        ''' Allow caller to set the upper-left corner icon used 
        ''' in the print preview dialog 
        ''' </summary> 
        Private ppvIcon As Icon = Nothing
        Public Property PreviewDialogIcon() As Icon
            Get
                Return ppvIcon
            End Get
            Set(ByVal value As Icon)
                ppvIcon = value
            End Set
        End Property

        ''' <summary> 
        ''' Flag to control whether or not we print the Page Header 
        ''' </summary> 
        Private m_printHeader As [Boolean] = True
        Public Property PrintHeader() As [Boolean]
            Get
                Return m_printHeader
            End Get
            Set(ByVal value As [Boolean])
                m_printHeader = value
            End Set
        End Property

        ''' <summary> 
        ''' Flag to control whether or not we print the Page Footer 
        ''' </summary> 
        Private m_printFooter As [Boolean] = True
        Public Property PrintFooter() As [Boolean]
            Get
                Return m_printFooter
            End Get
            Set(ByVal value As [Boolean])
                m_printFooter = value
            End Set
        End Property

        ''' <summary> 
        ''' Flag to control whether or not we print the Column Header line 
        ''' </summary> 
        Private m_printColumnHeaders As [Boolean] = True
        Public Property PrintColumnHeaders() As [Boolean]
            Get
                Return m_printColumnHeaders
            End Get
            Set(ByVal value As [Boolean])
                m_printColumnHeaders = value
            End Set
        End Property


#End Region

        ' Title 
#Region "title properties"

        ' override flag 
        Private overridetitleformat As Boolean = False

        ''' <summary> 
        ''' Title for this report. Default is empty. 
        ''' </summary> 
        Private m_title As [String]
        Public Property Title() As [String]
            Get
                Return m_title
            End Get
            Set(ByVal value As [String])
                m_title = value
                If m_docName Is Nothing Then
                    printDoc.DocumentName = value
                End If
            End Set
        End Property

        ''' <summary> 
        ''' Name of the document. Default is report title (can be empty) 
        ''' </summary> 
        Private m_docName As [String]
        Public Property DocName() As [String]
            Get
                Return m_docName
            End Get
            Set(ByVal value As [String])
                printDoc.DocumentName = value
                m_docName = value
            End Set
        End Property

        ''' <summary> 
        ''' Font for the title. Default is Tahoma, 18pt. 
        ''' </summary> 
        Private m_titlefont As Font
        Public Property TitleFont() As Font
            Get
                Return m_titlefont
            End Get
            Set(ByVal value As Font)
                m_titlefont = value
            End Set
        End Property

        ''' <summary> 
        ''' Foreground color for the title. Default is Black 
        ''' </summary> 
        Private m_titlecolor As Color
        Public Property TitleColor() As Color
            Get
                Return m_titlecolor
            End Get
            Set(ByVal value As Color)
                m_titlecolor = value
            End Set
        End Property

        ''' <summary> 
        ''' Allow override of the header cell format object 
        ''' </summary> 
        Private m_titleformat As StringFormat
        Public Property TitleFormat() As StringFormat
            Get
                Return m_titleformat
            End Get
            Set(ByVal value As StringFormat)
                m_titleformat = value
                overridetitleformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the title string alignment. Default value is 
        ''' Alignment - Near; 
        ''' </summary> 
        Public Property TitleAlignment() As StringAlignment
            Get
                Return m_titleformat.Alignment
            End Get
            Set(ByVal value As StringAlignment)
                m_titleformat.Alignment = value
                overridetitleformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the title string format flags. Default values 
        ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
        ''' </summary> 
        Public Property TitleFormatFlags() As StringFormatFlags
            Get
                Return m_titleformat.FormatFlags
            End Get
            Set(ByVal value As StringFormatFlags)
                m_titleformat.FormatFlags = value
                overridetitleformat = True
            End Set
        End Property
#End Region

        ' SubTitle 
#Region "subtitle properties"

        ' override flat 
        Private overridesubtitleformat As Boolean = False

        ''' <summary> 
        ''' SubTitle for this report. Default is empty. 
        ''' </summary> 
        Private m_subtitle As [String]
        Public Property SubTitle() As [String]
            Get
                Return m_subtitle
            End Get
            Set(ByVal value As [String])
                m_subtitle = value
            End Set
        End Property

        ''' <summary> 
        ''' Font for the subtitle. Default is Tahoma, 12pt. 
        ''' </summary> 
        Private m_subtitlefont As Font
        Public Property SubTitleFont() As Font
            Get
                Return m_subtitlefont
            End Get
            Set(ByVal value As Font)
                m_subtitlefont = value
            End Set
        End Property

        ''' <summary> 
        ''' Foreground color for the subtitle. Default is Black 
        ''' </summary> 
        Private m_subtitlecolor As Color
        Public Property SubTitleColor() As Color
            Get
                Return m_subtitlecolor
            End Get
            Set(ByVal value As Color)
                m_subtitlecolor = value
            End Set
        End Property

        ''' <summary> 
        ''' Allow override of the header cell format object 
        ''' </summary> 
        Private m_subtitleformat As StringFormat
        Public Property SubTitleFormat() As StringFormat
            Get
                Return m_subtitleformat
            End Get
            Set(ByVal value As StringFormat)
                m_subtitleformat = value
                overridesubtitleformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the subtitle string alignment. Default value is 
        ''' Alignment - Near; 
        ''' </summary> 
        Public Property SubTitleAlignment() As StringAlignment
            Get
                Return m_subtitleformat.Alignment
            End Get
            Set(ByVal value As StringAlignment)
                m_subtitleformat.Alignment = value
                overridesubtitleformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the subtitle string format flags. Default values 
        ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
        ''' </summary> 
        Public Property SubTitleFormatFlags() As StringFormatFlags
            Get
                Return m_subtitleformat.FormatFlags
            End Get
            Set(ByVal value As StringFormatFlags)
                m_subtitleformat.FormatFlags = value
                overridesubtitleformat = True
            End Set
        End Property
#End Region

        ' Footer 
#Region "footer properties"

        ' override flag 
        Private overridefooterformat As Boolean = False

        ''' <summary> 
        ''' footer for this report. Default is empty. 
        ''' </summary> 
        Private m_footer As [String]
        Public Property Footer() As [String]
            Get
                Return m_footer
            End Get
            Set(ByVal value As [String])
                m_footer = value
            End Set
        End Property

        ''' <summary> 
        ''' Font for the footer. Default is Tahoma, 10pt. 
        ''' </summary> 
        Private m_footerfont As Font
        Public Property FooterFont() As Font
            Get
                Return m_footerfont
            End Get
            Set(ByVal value As Font)
                m_footerfont = value
            End Set
        End Property

        ''' <summary> 
        ''' Foreground color for the footer. Default is Black 
        ''' </summary> 
        Private m_footercolor As Color
        Public Property FooterColor() As Color
            Get
                Return m_footercolor
            End Get
            Set(ByVal value As Color)
                m_footercolor = value
            End Set
        End Property

        ''' <summary> 
        ''' Allow override of the header cell format object 
        ''' </summary> 
        Private m_footerformat As StringFormat
        Public Property FooterFormat() As StringFormat
            Get
                Return m_footerformat
            End Get
            Set(ByVal value As StringFormat)
                m_footerformat = value
                overridefooterformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the footer string alignment. Default value is 
        ''' Alignment - Center; 
        ''' </summary> 
        Public Property FooterAlignment() As StringAlignment
            Get
                Return m_footerformat.Alignment
            End Get
            Set(ByVal value As StringAlignment)
                m_footerformat.Alignment = value
                overridefooterformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the footer string format flags. Default values 
        ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
        ''' </summary> 
        Public Property FooterFormatFlags() As StringFormatFlags
            Get
                Return m_footerformat.FormatFlags
            End Get
            Set(ByVal value As StringFormatFlags)
                m_footerformat.FormatFlags = value
                overridefooterformat = True
            End Set
        End Property

        Private m_footerspacing As Single
        Public Property FooterSpacing() As Single
            Get
                Return m_footerspacing
            End Get
            Set(ByVal value As Single)
                m_footerspacing = value
            End Set
        End Property

#End Region

        ' Page Numbering 
#Region "page number properties"

        ' override flag 
        Private overridepagenumberformat As Boolean = False

        ''' <summary> 
        ''' Include page number in the printout. Default is true. 
        ''' </summary> 
        Private pageno As Boolean = True
        Public Property PageNumbers() As Boolean
            Get
                Return pageno
            End Get
            Set(ByVal value As Boolean)
                pageno = value
            End Set
        End Property

        ''' <summary> 
        ''' Font for the page number, Default is Tahoma, 8pt. 
        ''' </summary> 
        Private pagenofont As Font
        Public Property PageNumberFont() As Font
            Get
                Return pagenofont
            End Get
            Set(ByVal value As Font)
                pagenofont = value
            End Set
        End Property

        ''' <summary> 
        ''' Text color (foreground) for the page number. Default is Black 
        ''' </summary> 
        Private pagenocolor As Color
        Public Property PageNumberColor() As Color
            Get
                Return pagenocolor
            End Get
            Set(ByVal value As Color)
                pagenocolor = value
            End Set
        End Property

        ''' <summary> 
        ''' Allow override of the header cell format object 
        ''' </summary> 
        Private m_pagenumberformat As StringFormat
        Public Property PageNumberFormat() As StringFormat
            Get
                Return m_pagenumberformat
            End Get
            Set(ByVal value As StringFormat)
                m_pagenumberformat = value
                overridepagenumberformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the page number string alignment. Default value is 
        ''' Alignment - Near; 
        ''' </summary> 
        Public Property PageNumberAlignment() As StringAlignment
            Get
                Return m_pagenumberformat.Alignment
            End Get
            Set(ByVal value As StringAlignment)
                m_pagenumberformat.Alignment = value
                overridepagenumberformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to override the pagenumber string format flags. Default values 
        ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
        ''' </summary> 
        Public Property PageNumberFormatFlags() As StringFormatFlags
            Get
                Return m_pagenumberformat.FormatFlags
            End Get
            Set(ByVal value As StringFormatFlags)
                m_pagenumberformat.FormatFlags = value
                overridepagenumberformat = True
            End Set
        End Property

        ''' <summary> 
        ''' Allow the user to select whether to have the page number at the top or bottom 
        ''' of the page. Default is false: page numbers on the bottom of the page 
        ''' </summary> 
        Private pagenumberontop As Boolean = False
        Public Property PageNumberInHeader() As Boolean
            Get
                Return pagenumberontop
            End Get
            Set(ByVal value As Boolean)
                pagenumberontop = value
            End Set
        End Property

        ''' <summary> 
        ''' Should the page number be printed on a separate line, or printed on the 
        ''' same line as the header / footer? Default is false; 
        ''' </summary> 
        Private m_pagenumberonseparateline As Boolean = False
        Public Property PageNumberOnSeparateLine() As Boolean
            Get
                Return m_pagenumberonseparateline
            End Get
            Set(ByVal value As Boolean)
                m_pagenumberonseparateline = value
            End Set
        End Property

        ''' <summary> 
        ''' Show the total page number as n of total 
        ''' </summary> 
        Private _totalpages As Integer
        Private m_showtotalpagenumber As Boolean = False
        Public Property ShowTotalPageNumber() As Boolean
            Get
                Return m_showtotalpagenumber
            End Get
            Set(ByVal value As Boolean)
                m_showtotalpagenumber = value
            End Set
        End Property

        ''' <summary> 
        ''' Text separating page number and total page number. Default is ' of '. 
        ''' </summary> 
        Private m_pageseparator As [String] = " of "
        Public Property PageSeparator() As [String]
            Get
                Return m_pageseparator
            End Get
            Set(ByVal value As [String])
                m_pageseparator = value
            End Set
        End Property

        Private m_pagetext As [String] = "Page "
        Public Property PageText() As [String]
            Get
                Return m_pagetext
            End Get
            Set(ByVal value As [String])
                m_pagetext = value
            End Set
        End Property

        Private m_parttext As [String] = " - Part "
        Public Property PartText() As [String]
            Get
                Return m_parttext
            End Get
            Set(ByVal value As [String])
                m_parttext = value
            End Set
        End Property

#End Region

        ' Header Cell Printing 
#Region "header cell properties"

        ''' <summary> 
        ''' Allow override of the header cell format object 
        ''' </summary> 
        Private headercellformat As StringFormat = Nothing
        Public Function GetHeaderCellFormat(ByVal grid As DataGridView) As StringFormat
            ' get default values from provided data grid view, but only 
            ' if we don't already have a header cell format 
            If (grid IsNot Nothing) AndAlso (headercellformat Is Nothing) Then
                buildstringformat(headercellformat, grid.Columns(0).HeaderCell.InheritedStyle, m_headercellalignment, StringAlignment.Near, m_headercellformatflags, StringTrimming.Word)
            End If

            ' if we still don't have a header cell format, create an empty 
            If headercellformat Is Nothing Then
                headercellformat = New StringFormat(m_headercellformatflags)
            End If

            Return headercellformat
        End Function

        ''' <summary> 
        ''' Deprecated - use HeaderCellFormat 
        ''' Allow the user to override the header cell string alignment. Default value is 
        ''' Alignment - Near; 
        ''' </summary> 
        Private m_headercellalignment As StringAlignment
        Public Property HeaderCellAlignment() As StringAlignment
            Get
                Return m_headercellalignment
            End Get
            Set(ByVal value As StringAlignment)
                m_headercellalignment = value
            End Set
        End Property

        ''' <summary> 
        ''' Deprecated - use HeaderCellFormat 
        ''' Allow the user to override the header cell string format flags. Default values 
        ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
        ''' </summary> 
        Private m_headercellformatflags As StringFormatFlags
        Public Property HeaderCellFormatFlags() As StringFormatFlags
            Get
                Return m_headercellformatflags
            End Get
            Set(ByVal value As StringFormatFlags)
                m_headercellformatflags = value
            End Set
        End Property
#End Region

        ' Individual Cell Printing 
#Region "cell properties"

        ''' <summary> 
        ''' Allow override of the cell printing format 
        ''' </summary> 
        Private cellformat As StringFormat = Nothing
        Public Function GetCellFormat(ByVal grid As DataGridView) As StringFormat
            ' get default values from provided data grid view, but only 
            ' if we don't already have a cell format 
            If (grid IsNot Nothing) AndAlso (cellformat Is Nothing) Then
                buildstringformat(cellformat, grid.Rows(0).Cells(0).InheritedStyle, m_cellalignment, StringAlignment.Near, m_cellformatflags, StringTrimming.Word)
            End If

            ' if we still don't have a cell format, create an empty 
            If cellformat Is Nothing Then
                cellformat = New StringFormat(m_cellformatflags)
            End If

            Return cellformat
        End Function

        ''' <summary> 
        ''' Deprecated - use GetCellFormat 
        ''' Allow the user to override the cell string alignment. Default value is 
        ''' Alignment - Near; 
        ''' </summary> 
        Private m_cellalignment As StringAlignment
        Public Property CellAlignment() As StringAlignment
            Get
                Return m_cellalignment
            End Get
            Set(ByVal value As StringAlignment)
                m_cellalignment = value
            End Set
        End Property

        ''' <summary> 
        ''' Deprecated - use GetCellFormat 
        ''' Allow the user to override the cell string format flags. Default values 
        ''' are: FormatFlags - NoWrap, LineLimit, NoClip 
        ''' </summary> 
        Private m_cellformatflags As StringFormatFlags
        Public Property CellFormatFlags() As StringFormatFlags
            Get
                Return m_cellformatflags
            End Get
            Set(ByVal value As StringFormatFlags)
                m_cellformatflags = value
            End Set
        End Property

        ''' <summary> 
        ''' allow the user to override the column width calcs with their own defaults 
        ''' </summary> 
        Private colwidthsoverride As New List(Of Single)()
        Private publicwidthoverrides As New Dictionary(Of String, Single)()
        Public ReadOnly Property ColumnWidths() As Dictionary(Of String, Single)
            Get
                Return publicwidthoverrides
            End Get
        End Property

        ''' <summary> 
        ''' Allow per column style overrides 
        ''' </summary> 
        Private colstyles As New Dictionary(Of String, DataGridViewCellStyle)()
        Public ReadOnly Property ColumnStyles() As Dictionary(Of String, DataGridViewCellStyle)
            Get
                Return colstyles
            End Get
        End Property

#End Region

        ' Page Level Properties 
#Region "page level properties"

        ''' <summary> 
        ''' Page margins override. Default is (60, 60, 40, 40) 
        ''' </summary> 
        Public Property PrintMargins() As Margins
            Get
                Return PageSettings.Margins
            End Get
            Set(ByVal value As Margins)
                PageSettings.Margins = value
            End Set
        End Property

        ''' <summary> 
        ''' Expose the printdocument default page settings to the caller 
        ''' </summary> 
        Public ReadOnly Property PageSettings() As PageSettings
            Get
                Return printDoc.DefaultPageSettings
            End Get
        End Property

        ''' <summary> 
        ''' Spread the columns porportionally accross the page. Default is false. 
        ''' </summary> 
        Private m_porportionalcolumns As Boolean = False
        Public Property PorportionalColumns() As Boolean
            Get
                Return m_porportionalcolumns
            End Get
            Set(ByVal value As Boolean)
                m_porportionalcolumns = value
            End Set
        End Property

        ''' <summary> 
        ''' Center the table on the page. 
        ''' </summary> 
        Public Enum Alignment
            NotSet
            Left
            Right
            Center
        End Enum
        Private m_tablealignment As Alignment = Alignment.NotSet
        Public Property TableAlignment() As Alignment
            Get
                Return m_tablealignment
            End Get
            Set(ByVal value As Alignment)
                m_tablealignment = value
            End Set
        End Property

        ''' <summary> 
        ''' Change the default row height to either the height of the string or the size of 
        ''' the cell. Added for image cell handling. 
        ''' </summary> 
        Public Enum RowHeightSetting
            StringHeight
            CellHeight
        End Enum
        Private _rowheight As RowHeightSetting = RowHeightSetting.StringHeight
        Public Property RowHeight() As RowHeightSetting
            Get
                Return _rowheight
            End Get
            Set(ByVal value As RowHeightSetting)
                _rowheight = value
            End Set
        End Property




#End Region

        ' Utility Functions 
#Region ""
        ''' <summary> 
        ''' calculate the print preview window width to show the entire page 
        ''' </summary> 
        ''' <returns></returns> 
        Private Function PreviewDisplayWidth() As Integer
            Dim displayWidth As Double = printDoc.DefaultPageSettings.Bounds.Width + 3 * printDoc.DefaultPageSettings.HardMarginY
            Return CInt((displayWidth * PrintPreviewZoom))
        End Function

        ''' <summary> 
        ''' calculate the print preview window height to show the entire page 
        ''' </summary> 
        ''' <returns></returns> 
        Private Function PreviewDisplayHeight() As Integer
            Dim displayHeight As Double = printDoc.DefaultPageSettings.Bounds.Height + 3 * printDoc.DefaultPageSettings.HardMarginX

            Return CInt((displayHeight * PrintPreviewZoom))
        End Function
#End Region

#End Region

        ''' <summary> 
        ''' Constructor for DGVPrinter 
        ''' </summary> 
        Public Sub New()
            ' create print document 
            printDoc = New PrintDocument()
            AddHandler printDoc.PrintPage, AddressOf printDoc_PrintPage
            AddHandler printDoc.BeginPrint, AddressOf printDoc_BeginPrint
            PrintMargins = New Margins(60, 60, 40, 40)

            ' set default fonts 
            pagenofont = New Font("Tahoma", 8, FontStyle.Regular, GraphicsUnit.Point)
            pagenocolor = Color.Black
            m_titlefont = New Font("Tahoma", 18, FontStyle.Bold, GraphicsUnit.Point)
            m_titlecolor = Color.Black
            m_subtitlefont = New Font("Tahoma", 12, FontStyle.Bold, GraphicsUnit.Point)
            m_subtitlecolor = Color.Black
            m_footerfont = New Font("Tahoma", 10, FontStyle.Bold, GraphicsUnit.Point)
            m_footercolor = Color.Black

            ' Create string formatting objects 
            buildstringformat(m_titleformat, Nothing, StringAlignment.Center, StringAlignment.Center, StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip, StringTrimming.Word)
            buildstringformat(m_subtitleformat, Nothing, StringAlignment.Center, StringAlignment.Center, StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip, StringTrimming.Word)
            buildstringformat(m_footerformat, Nothing, StringAlignment.Center, StringAlignment.Center, StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip, StringTrimming.Word)
            buildstringformat(m_pagenumberformat, Nothing, StringAlignment.Far, StringAlignment.Center, StringFormatFlags.NoWrap Or StringFormatFlags.LineLimit Or StringFormatFlags.NoClip, StringTrimming.Word)

            ' Set these formatting objects to null to flag whether or not they were set by the caller 
            headercellformat = Nothing
            cellformat = Nothing

            ' Print Preview properties 
            Owner = Nothing
            PrintPreviewZoom = 1.0R

            ' Deprecated properties - retain for backwards compatibility 
            m_headercellalignment = StringAlignment.Near
            m_headercellformatflags = StringFormatFlags.LineLimit Or StringFormatFlags.NoClip
            m_cellalignment = StringAlignment.Near
            m_cellformatflags = StringFormatFlags.LineLimit Or StringFormatFlags.NoClip
        End Sub


        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 
        ' Primary Interface - Presents a dialog and then prints or previews the 
        ' indicated data grid view 
        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 

        ''' <summary> 
        ''' Start the printing process, print to a printer. 
        ''' </summary> 
        ''' <param name="dgv">The DataGridView to print</param> 
        ''' NOTE: Any changes to this method also need to be done in PrintPreviewDataGridView 
        Public Sub PrintDataGridView(ByVal dgv As DataGridView)
            If dgv Is Nothing Then
                Throw New Exception("Null Parameter passed to DGVPrinter.")
            End If
            If Not GetType(DataGridView) Is dgv.GetType Then
                Throw New Exception("Invalid Parameter passed to DGVPrinter.")
            End If

            ' save the datagridview we're printing 
            Me.dgv = dgv

            ' display dialog and print 
            If DialogResult.OK = DisplayPrintDialog() Then
                SetupPrint()
                printDoc.Print()
            End If
        End Sub

        ''' <summary> 
        ''' Start the printing process, print to a print preview dialog 
        ''' </summary> 
        ''' <param name="dgv">The DataGridView to print</param> 
        ''' NOTE: Any changes to this method also need to be done in PrintDataGridView 
        Public Sub PrintPreviewDataGridView(ByVal dgv As DataGridView)
            If dgv Is Nothing Then
                Throw New Exception("Null Parameter passed to DGVPrinter.")
            End If
            If Not GetType(DataGridView) Is dgv.[GetType] Then
                Throw New Exception("Invalid Parameter passed to DGVPrinter.")
            End If

            ' save the datagridview we're printing 
            Me.dgv = dgv

            ' display dialog and print 
            If DialogResult.OK = DisplayPrintDialog() Then

                'SetupPrint(); 
                'PrintPreviewDialog ppdialog = new PrintPreviewDialog(); 
                'ppdialog.Document = printDoc; 
                'ppdialog.UseAntiAlias = true; 
                'ppdialog.Owner = Owner; 
                'ppdialog.PrintPreviewControl.Zoom = PrintPreviewZoom; 
                'ppdialog.Width = PreviewDisplayWidth(); 
                'ppdialog.Height = PreviewDisplayHeight(); 

                'if (null != ppvIcon) 
                ' ppdialog.Icon = ppvIcon; 
                'ppdialog.ShowDialog(); 
                PrintPreviewNoDisplay(dgv)
            End If
        End Sub

        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 
        ' Alternative Interface. In order to set the print information correctly 
        ' either the DisplayPrintDialog() routine must be called, OR the 
        ' PrintDocument (and PrinterSettings) must be handled through calling 
        ' PrintDialog separately. 
        ' 
        ' Once the PrintDocument has been setup, the PrintNoDisplay() and/or 
        ' PrintPreviewNoDisplay() routines can be called to print multiple 
        ' DataGridViews using the same print setup. 
        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 

        ''' <summary> 
        ''' Display a printdialog and return the result. Either this method or 
        ''' the equivalent must be done prior to calling either of the PrintNoDisplay 
        ''' or PrintPreviewNoDisplay methods. 
        ''' </summary> 
        ''' <returns></returns> 
        Public Function DisplayPrintDialog() As DialogResult
            ' create new print dialog and set options 
            Dim pd As New PrintDialog()
            pd.UseEXDialog = m_printDialogSettings.UseEXDialog
            pd.AllowSelection = m_printDialogSettings.AllowSelection
            pd.AllowSomePages = m_printDialogSettings.AllowSomePages
            pd.AllowCurrentPage = m_printDialogSettings.AllowCurrentPage
            pd.AllowPrintToFile = m_printDialogSettings.AllowPrintToFile
            pd.ShowHelp = m_printDialogSettings.ShowHelp
            pd.ShowNetwork = m_printDialogSettings.ShowNetwork

            ' setup print dialog with internal setttings 
            pd.Document = printDoc
            If Not [String].IsNullOrEmpty(m_printerName) Then
                printDoc.PrinterSettings.PrinterName = m_printerName
            End If

            ' Ensure default landscape setting and papersize setting match print dialog's 
            printDoc.DefaultPageSettings.Landscape = pd.PrinterSettings.DefaultPageSettings.Landscape
            printDoc.DefaultPageSettings.PaperSize = New PaperSize(pd.PrinterSettings.DefaultPageSettings.PaperSize.PaperName, pd.PrinterSettings.DefaultPageSettings.PaperSize.Width, pd.PrinterSettings.DefaultPageSettings.PaperSize.Height)

            ' show the dialog and display the result 
            Return pd.ShowDialog()
        End Function

        ''' <summary> 
        ''' Print the provided grid view. Either DisplayPrintDialog() or it's equivalent 
        ''' setup must be completed prior to calling this routine 
        ''' </summary> 
        ''' <param name="dgv"></param> 
        Public Sub PrintNoDisplay(ByVal dgv As DataGridView)
            If dgv Is Nothing Then
                Throw New Exception("Null Parameter passed to DGVPrinter.")
            End If
            If Not GetType(DataGridView) Is dgv.[GetType] Then
                Throw New Exception("Invalid Parameter passed to DGVPrinter.")
            End If

            ' save the grid we're printing 
            Me.dgv = dgv

            ' setup and do printing 
            SetupPrint()
            printDoc.Print()
        End Sub

        ''' <summary> 
        ''' Preview the provided grid view. Either DisplayPrintDialog() or it's equivalent 
        ''' setup must be completed prior to calling this routine 
        ''' </summary> 
        ''' <param name="dgv"></param> 
        Public Sub PrintPreviewNoDisplay(ByVal dgv As DataGridView)
            If dgv Is Nothing Then
                Throw New Exception("Null Parameter passed to DGVPrinter.")
            End If
            If Not GetType(DataGridView) Is dgv.[GetType]() Then
                Throw New Exception("Invalid Parameter passed to DGVPrinter.")
            End If

            ' save the grid we're printing 
            Me.dgv = dgv

            ' display the preview dialog 
            SetupPrint()
            Dim ppdialog As New PrintPreviewDialog()
            ppdialog.Document = printDoc
            ppdialog.UseAntiAlias = True
            ppdialog.Owner = Owner
            ppdialog.PrintPreviewControl.Zoom = PrintPreviewZoom
            ppdialog.Width = PreviewDisplayWidth()
            ppdialog.Height = PreviewDisplayHeight()

            If ppvIcon IsNot Nothing Then
                ppdialog.Icon = ppvIcon
            End If
            ppdialog.ShowDialog()
        End Sub


        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 
        ' Print Process Interface Methods 
        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 

        Public Function EmbeddedPrint(ByVal dgv As DataGridView, ByVal g As Graphics, ByVal area As Rectangle) As Boolean
            ' verify we've been set up properly 
            If (dgv Is Nothing) OrElse (g Is Nothing) Then
                Throw New Exception("Null Parameter passed to DGVPrinter.")
            End If

            ' save the grid we're printing 
            Me.dgv = dgv

            '----------------------------------------------------------------- 
            ' do a mini version of SetupPrint for printing into an already 
            ' provided graphics context rather than owning the entire print process 
            '----------------------------------------------------------------- 

            ' set margins so we print within the provided area 
            Dim saveMargins As Margins = PrintMargins
            PrintMargins.Top = area.Top
            PrintMargins.Bottom = 0
            PrintMargins.Left = area.Left
            PrintMargins.Right = 0

            ' set "page" height and width to our destination area 
            pageHeight = area.Height + area.Top
            printWidth = area.Width
            pageWidth = area.Width + area.Left

            ' initially assume we'll eventually print all records 
            fromPage = 0
            toPage = 2147483647

            ' force 'off' header and footer 
            PrintHeader = False
            PrintFooter = False

            ' set cell format 
            If cellformat Is Nothing Then
                buildstringformat(cellformat, dgv.DefaultCellStyle, m_cellalignment, StringAlignment.Near, m_cellformatflags, StringTrimming.Word)
            End If

            ' select all visible rows and all visible columns 
            rowstoprint = New List(Of Object)(dgv.Rows.Count)
            For Each row As DataGridViewRow In dgv.Rows
                If row.Visible Then
                    rowstoprint.Add(row)
                End If
            Next

            colstoprint = New List(Of Object)(dgv.Columns.Count)
            For Each col As DataGridViewColumn In dgv.Columns
                If col.Visible Then
                    colstoprint.Add(col)
                End If
            Next

            ' Reorder columns based on Display Index (if the programmer or user has 
            ' changed the column display order we want to respect it in the printout) 
            Dim displayorderlist As New SortedList(colstoprint.Count)
            For Each col As DataGridViewColumn In colstoprint
                displayorderlist.Add(col.DisplayIndex, col)
            Next
            colstoprint.Clear()
            For Each item As Object In displayorderlist.Values
                colstoprint.Add(item)
            Next

            ' Adjust override list to have the same number of entries as colstoprint 
            For Each col As DataGridViewColumn In colstoprint
                If publicwidthoverrides.ContainsKey(col.Name) Then
                    colwidthsoverride.Add(publicwidthoverrides(col.Name))
                Else
                    colwidthsoverride.Add(-1)
                End If
            Next

            '----------------------------------------------------------------- 
            ' Now that we know what we're printing, measure the print area and 
            ' count the pages. 
            '----------------------------------------------------------------- 

            ' Measure the print area 
            measureprintarea(g)

            ' Count the pages 
            _totalpages = totalpages()

            ' set counter values 
            currentpageset = 0
            lastrowprinted = -1
            CurrentPage = 0

            ' call the print engine 
            Return PrintPage(g)

            ' reset printer margins 
            PrintMargins = saveMargins
        End Function

        ''' <summary> 
        ''' BeginPrint Event Handler 
        ''' Set values at start of print run 
        ''' </summary> 
        ''' <param name="sender"></param> 
        ''' <param name="e"></param> 
        Private Sub printDoc_BeginPrint(ByVal sender As Object, ByVal e As PrintEventArgs)
            ' reset counters since we'll go through this twice if we print from preview 
            currentpageset = 0
            lastrowprinted = -1
            CurrentPage = 0
        End Sub

        ''' <summary> 
        ''' PrintPage event handler. This routine prints one page. It will 
        ''' skip non-printable pages if the user selected the "some pages" option 
        ''' on the print dialog. 
        ''' </summary> 
        ''' <param name="sender">default object from windows</param> 
        ''' <param name="e">Event info from Windows about the printing</param> 
        Private Sub printDoc_PrintPage(ByVal sender As Object, ByVal e As PrintPageEventArgs)
            e.HasMorePages = PrintPage(e.Graphics)
        End Sub


        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 
        ' Internal Methods 
        '--------------------------------------------------------------------- 
        '--------------------------------------------------------------------- 

        ''' <summary> 
        ''' Set up the print job. Save information from print dialog 
        ''' and print document for easy access. Also sets up the rows 
        ''' and columns that will be printed. At this point, we're 
        ''' collecting all columns in colstoprint. This will be broken 
        ''' up into pagesets later on 
        ''' </summary> 
        Private Sub SetupPrint()
            '----------------------------------------------------------------- 
            ' Set headercell and normal cell print formats if they were not 
            ' explicitly set by the caller 
            '----------------------------------------------------------------- 
            If headercellformat Is Nothing Then
                buildstringformat(headercellformat, dgv.Columns(0).HeaderCell.InheritedStyle, m_headercellalignment, StringAlignment.Near, m_headercellformatflags, StringTrimming.Word)
            End If
            If cellformat Is Nothing Then
                buildstringformat(cellformat, dgv.DefaultCellStyle, m_cellalignment, StringAlignment.Near, m_cellformatflags, StringTrimming.Word)
            End If

            '----------------------------------------------------------------- 
            ' get info on the limits of the printer's actual print area available. Convert 
            ' to int's to work with margins. 
            '----------------------------------------------------------------- 
            Dim hardx As Integer = CInt(Math.Round(printDoc.DefaultPageSettings.HardMarginX))
            Dim hardy As Integer = CInt(Math.Round(printDoc.DefaultPageSettings.HardMarginY))
            Dim printareawidth As Integer
            If printDoc.DefaultPageSettings.Landscape Then
                printareawidth = CInt(Math.Round(printDoc.DefaultPageSettings.PrintableArea.Height))
            Else
                printareawidth = CInt(Math.Round(printDoc.DefaultPageSettings.PrintableArea.Width))
            End If

            '----------------------------------------------------------------- 
            ' set the print area we're working within 
            '----------------------------------------------------------------- 

            pageHeight = printDoc.DefaultPageSettings.Bounds.Height
            pageWidth = printDoc.DefaultPageSettings.Bounds.Width

            '----------------------------------------------------------------- 
            ' Set the printable area: margins and pagewidth 
            '----------------------------------------------------------------- 

            ' Set initial printer margins 
            PrintMargins = printDoc.DefaultPageSettings.Margins

            ' adjust for when the margins are less than the printer's hard x/y limits 
            PrintMargins.Right = IIf((hardx > PrintMargins.Right), hardx, PrintMargins.Right)
            PrintMargins.Left = IIf((hardx > PrintMargins.Left), hardx, PrintMargins.Left)
            PrintMargins.Top = IIf((hardy > PrintMargins.Top), hardy, PrintMargins.Top)
            PrintMargins.Bottom = IIf((hardy > PrintMargins.Bottom), hardy, PrintMargins.Bottom)

            ' Now, we can calc default print width, again, respecting the printer's limitations 
            printWidth = pageWidth - PrintMargins.Left - PrintMargins.Right
            printWidth = IIf((printWidth > printareawidth), printareawidth, printWidth)

            '----------------------------------------------------------------- 
            ' Figure out which pages / rows to print 
            '----------------------------------------------------------------- 

            ' save print range 
            printRange = printDoc.PrinterSettings.PrintRange

            ' pages to print handles "some pages" option 
            If printRange.SomePages = printRange Then
                ' set limits to only print some pages 
                fromPage = printDoc.PrinterSettings.FromPage
                toPage = printDoc.PrinterSettings.ToPage
            Else
                ' set extremes so that we'll print all pages 
                fromPage = 0
                toPage = 2147483647
            End If

            '----------------------------------------------------------------- 
            ' set up the rows and columns to print 
            ' 
            ' Note: The "Selectedxxxx" lists in the datagridview are 'stacks' that 
            ' have the selected items pushed in the *in the order they were selected* 
            ' i.e. not the order you want to print them in! 
            '----------------------------------------------------------------- 

            ' rows to print (handles "selection" and "current page" options 
            If printRange.Selection = printRange Then
                Dim temprowstoprint As SortedList
                Dim tempcolstoprint As SortedList

                'if DGV has rows selected, it's easy, selected rows and all visible columns 
                If 0 <> dgv.SelectedRows.Count Then
                    ' sort the rows into index order 
                    temprowstoprint = New SortedList(dgv.SelectedRows.Count)
                    For Each row As DataGridViewRow In dgv.SelectedRows
                        temprowstoprint.Add(row.Index, row)
                    Next

                    Dim ie As IEnumerator = temprowstoprint.Values.GetEnumerator()

                    rowstoprint = New List(Of Object)(temprowstoprint.Count)
                    For Each item As Object In temprowstoprint.Values
                        rowstoprint.Add(item)
                    Next

                    colstoprint = New List(Of Object)(dgv.Columns.Count)
                    For Each col As DataGridViewColumn In dgv.Columns
                        If col.Visible Then
                            colstoprint.Add(col)
                        End If
                    Next
                    ' if selected columns, then all rows, and selected columns 
                ElseIf 0 <> dgv.SelectedColumns.Count Then
                    rowstoprint = dgv.Rows

                    tempcolstoprint = New SortedList(dgv.SelectedColumns.Count)
                    For Each row As DataGridViewRow In dgv.SelectedColumns
                        tempcolstoprint.Add(row.Index, row)
                    Next

                    colstoprint = New List(Of Object)(tempcolstoprint.Count)
                    For Each item As Object In tempcolstoprint.Values
                        colstoprint.Add(item)
                    Next
                Else
                    ' we just have a bunch of selected cells so we have to do some work 
                    ' set up sorted lists. the selectedcells method does not guarantee 
                    ' that the cells will always be in left-right top-bottom order. 
                    temprowstoprint = New SortedList(dgv.SelectedCells.Count)
                    tempcolstoprint = New SortedList(dgv.SelectedCells.Count)

                    ' for each selected cell, add unique rows and columns 
                    Dim colindex As Integer, rowindex As Integer
                    For Each cell As DataGridViewCell In dgv.SelectedCells
                        colindex = cell.ColumnIndex
                        rowindex = cell.RowIndex

                        ' add unique rows 
                        If Not temprowstoprint.Contains(rowindex) Then
                            temprowstoprint.Add(rowindex, dgv.Rows(rowindex))
                        End If

                        ' add unique columns 
                        If Not tempcolstoprint.Contains(colindex) Then
                            tempcolstoprint.Add(colindex, dgv.Columns(colindex))
                        End If
                    Next

                    ' Move the now-duplicate free columns and rows to our list of what to print 
                    rowstoprint = New List(Of Object)(temprowstoprint.Count)
                    For Each item As Object In temprowstoprint.Values
                        rowstoprint.Add(item)
                    Next
                    colstoprint = New List(Of Object)(tempcolstoprint.Count)
                    For Each item As Object In tempcolstoprint.Values
                        colstoprint.Add(item)
                    Next
                End If
                ' if current page was selected, print visible columns for the 
                ' displayed rows 
            ElseIf printRange.CurrentPage = printRange Then
                ' create lists 
                rowstoprint = New List(Of Object)(dgv.DisplayedRowCount(True))
                colstoprint = New List(Of Object)(dgv.Columns.Count)

                ' select all visible rows on displayed page 
                For i As Integer = dgv.FirstDisplayedScrollingRowIndex To dgv.FirstDisplayedScrollingRowIndex + (dgv.DisplayedRowCount(True) - 1)
                    Dim row As DataGridViewRow = dgv.Rows(i)
                    If row.Visible Then
                        rowstoprint.Add(row)
                    End If
                Next

                ' select all visible columns 
                colstoprint = New List(Of Object)(dgv.Columns.Count)
                For Each col As DataGridViewColumn In dgv.Columns
                    If col.Visible Then
                        colstoprint.Add(col)
                    End If
                Next
            Else
                ' this is the default for print all - everything marked visible will be printed 
                ' select all visible rows and all visible columns 
                rowstoprint = New List(Of Object)(dgv.Rows.Count)
                For Each row As DataGridViewRow In dgv.Rows
                    If row.Visible Then
                        rowstoprint.Add(row)
                    End If
                Next

                colstoprint = New List(Of Object)(dgv.Columns.Count)
                For Each col As DataGridViewColumn In dgv.Columns
                    If col.Visible Then
                        colstoprint.Add(col)
                    End If
                Next
            End If

            ' Reorder columns based on Display Index (if the programmer or user has 
            ' changed the column display order we want to respect it in the printout) 
            Dim displayorderlist As New SortedList(colstoprint.Count)
            For Each col As DataGridViewColumn In colstoprint
                displayorderlist.Add(col.DisplayIndex, col)
            Next
            colstoprint.Clear()
            For Each item As Object In displayorderlist.Values
                colstoprint.Add(item)
            Next

            ' Adjust override list to have the same number of entries as colstoprint 
            For Each col As DataGridViewColumn In colstoprint
                If publicwidthoverrides.ContainsKey(col.Name) Then
                    colwidthsoverride.Add(publicwidthoverrides(col.Name))
                Else
                    colwidthsoverride.Add(-1)
                End If
            Next

            '----------------------------------------------------------------- 
            ' Now that we know what we're printing, measure the print area and 
            ' count the pages. 
            '----------------------------------------------------------------- 

            ' Measure the print area 
            measureprintarea(printDoc.PrinterSettings.CreateMeasurementGraphics())

            ' Count the pages 

            _totalpages = totalpages()
        End Sub

        ''' <summary> 
        ''' Centralize the string format settings. Build a string format object 
        ''' using passed in settings, (allowing a user override of a single setting) 
        ''' and get the alignment from the cell control style. 
        ''' </summary> 
        ''' <param name="format">String format, ref parameter with return settings</param> 
        ''' <param name="controlstyle">DataGridView style to apply (if available)</param> 
        ''' <param name="alignment">Override text Alignment</param> 
        ''' <param name="linealignment">Override line alignment</param> 
        ''' <param name="flags">String format flags</param> 
        ''' <param name="trim">Override string trimming flags</param> 
        ''' <returns></returns> 
        Private Sub buildstringformat(ByRef format As StringFormat, ByVal controlstyle As DataGridViewCellStyle, ByVal alignment As StringAlignment, ByVal linealignment As StringAlignment, ByVal flags As StringFormatFlags, ByVal trim As StringTrimming)
            ' allocate format if it doesn't already exist 
            If format Is Nothing Then
                format = New StringFormat()
            End If

            ' Set defaults 
            format.Alignment = alignment
            format.LineAlignment = linealignment
            format.FormatFlags = flags
            format.Trimming = trim

            ' use cell alignment to override defaulted alignments 
            If controlstyle IsNot Nothing Then
                ' Adjust the format based on the control settings, bias towards centered 
                Dim cellalign As DataGridViewContentAlignment = controlstyle.Alignment
                If cellalign.ToString().Contains("Center") Then
                    format.Alignment = StringAlignment.Center
                ElseIf cellalign.ToString().Contains("Left") Then
                    format.Alignment = StringAlignment.Near
                ElseIf cellalign.ToString().Contains("Right") Then
                    format.Alignment = StringAlignment.Far
                End If

                If cellalign.ToString().Contains("Top") Then
                    format.LineAlignment = StringAlignment.Near
                ElseIf cellalign.ToString().Contains("Middle") Then
                    format.LineAlignment = StringAlignment.Center
                ElseIf cellalign.ToString().Contains("Bottom") Then
                    format.LineAlignment = StringAlignment.Far
                End If
            End If
        End Sub

        ''' <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 Sub measureprintarea(ByVal g As Graphics)
            Dim i As Integer, j As Integer
            rowheights = New List(Of Single)(rowstoprint.Count)
            colwidths = New List(Of Single)(colstoprint.Count)
            headerHeight = 0
            footerHeight = 0

            ' temp variables 
            Dim col As DataGridViewColumn
            Dim row As DataGridViewRow

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

            ' measure the column headers 
            Dim headerfont As Font = dgv.ColumnHeadersDefaultCellStyle.Font
            If headerfont Is Nothing Then
                headerfont = dgv.DefaultCellStyle.Font
            End If

            ' set initial column sizes based on column titles 
            For i = 0 To colstoprint.Count - 1
                col = DirectCast(colstoprint(i), DataGridViewColumn)

                ' deal with overridden col widths 
                Dim usewidth As Single = 0
                If 0 < colwidthsoverride(i) Then
                    usewidth = colwidthsoverride(i)
                Else
                    usewidth = printWidth
                End If

                ' measure the title for each column, keep widths and biggest height 
                Dim size As SizeF = g.MeasureString(col.HeaderText, headerfont, New SizeF(usewidth, 2147483647), headercellformat)
                colwidths.Add(size.Width)
                colheaderheight = (IIf(colheaderheight < size.Height, size.Height, colheaderheight))
            Next

            '----------------------------------------------------------------- 
            ' measure the page number 
            '----------------------------------------------------------------- 

            If pageno Then
                pagenumberHeight = (g.MeasureString("Page", pagenofont, printWidth, m_pagenumberformat)).Height
            End If

            '----------------------------------------------------------------- 
            ' Calc height of header. 
            ' Header height is height of page number, title, subtitle and height of column headers 
            '----------------------------------------------------------------- 
            If PrintHeader Then

                ' note that we dont count the page number height if it's not on a separate line 
                If pagenumberontop AndAlso Not m_pagenumberonseparateline Then
                    headerHeight += pagenumberHeight
                End If

                If Not [String].IsNullOrEmpty(m_title) Then
                    headerHeight += (g.MeasureString(m_title, m_titlefont, printWidth, m_titleformat)).Height
                End If

                If Not [String].IsNullOrEmpty(m_subtitle) Then
                    headerHeight += (g.MeasureString(m_subtitle, m_subtitlefont, printWidth, m_subtitleformat)).Height
                End If

                headerHeight += colheaderheight
            End If

            '----------------------------------------------------------------- 
            ' measure the footer, if one is provided. Include the page number if we're printing 
            ' it on the bottom 
            '----------------------------------------------------------------- 
            If PrintFooter Then
                If Not [String].IsNullOrEmpty(m_footer) Then
                    footerHeight += (g.MeasureString(m_footer, m_footerfont, printWidth, m_footerformat)).Height
                End If

                ' note we don't count the page number height if it's not on a separate line 
                If Not pagenumberontop AndAlso m_pagenumberonseparateline Then
                    footerHeight += pagenumberHeight
                End If

                footerHeight += m_footerspacing
            End If

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

            For i = 0 To rowstoprint.Count - 1
                row = DirectCast(rowstoprint(i), DataGridViewRow)
                rowheights.Add(0)

                ' add row headers if they're visible 
                If dgv.RowHeadersVisible Then
                    Dim rhsize As SizeF = g.MeasureString(row.HeaderCell.EditedFormattedValue.ToString(), headerfont)
                    rowheaderwidth = IIf((rowheaderwidth < rhsize.Width), rhsize.Width, rowheaderwidth)
                End If

                ' calculate widths for each column. We're looking for the largest width needed for 
                ' all the rows of data. 
                For j = 0 To colstoprint.Count - 1
                    col = DirectCast(colstoprint(j), DataGridViewColumn)

                    ' access the data to be printed - weird bug: had to move this up here since 
                    ' doing this access actually changes the cell's style. ??? 
                    Dim datastr As [String] = row.Cells(col.Index).EditedFormattedValue.ToString()

                    ' get gridview style, and override if we have a set style for this column 
                    Dim currentformat As StringFormat = Nothing
                    Dim colstyle As DataGridViewCellStyle = Nothing
                    If ColumnStyles.ContainsKey(col.Name) Then
                        colstyle = colstyles(col.Name)

                        ' build the cell style and font 
                        buildstringformat(currentformat, colstyle, cellformat.Alignment, cellformat.LineAlignment, cellformat.FormatFlags, cellformat.Trimming)
                    ElseIf (col.HasDefaultCellStyle) OrElse (row.Cells(col.Index).HasStyle) Then
                        colstyle = row.Cells(col.Index).InheritedStyle

                        ' build the cell style and font 
                        buildstringformat(currentformat, colstyle, cellformat.Alignment, cellformat.LineAlignment, cellformat.FormatFlags, cellformat.Trimming)
                    Else
                        currentformat = cellformat
                        colstyle = dgv.DefaultCellStyle
                    End If

                    ' get the default size based on the RowHeight setting 
                    Dim size As SizeF
                    If RowHeightSetting.CellHeight = RowHeight Then
                        size = row.Cells(col.Index).Size
                    Else
                        size = g.MeasureString(datastr, colstyle.Font)
                    End If

                    ' 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 for lots of text) 
                    If (0 < colwidthsoverride(j)) OrElse (size.Width > printWidth) Then
                        ' set column width 
                        If 0 < colwidthsoverride(j) Then
                            colwidths(j) = colwidthsoverride(j)
                        ElseIf size.Width > printWidth Then
                            colwidths(j) = printWidth
                        End If

                        ' 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 
                        Dim chars As Integer, lines As Integer
                        size = g.MeasureString(datastr, colstyle.Font, New SizeF(colwidths(j), 2147483647), currentformat, chars, lines)

                        ' set row height 
                        Dim tempheight As Single = size.Height
                        ' lines * size.Height; 
                        rowheights(i) = (IIf(rowheights(i) < tempheight, tempheight, rowheights(i)))
                    Else
                        colwidths(j) = (IIf(colwidths(j) < size.Width, size.Width, colwidths(j)))
                        rowheights(i) = (IIf(rowheights(i) < size.Height, size.Height, rowheights(i)))
                    End If
                Next
            Next

            '----------------------------------------------------------------- 
            ' 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(Of PageDef)()
            pagesets.Add(New PageDef(PrintMargins, colstoprint.Count))
            Dim pset As Integer = 0

            ' Account for row headers 
            pagesets(pset).coltotalwidth = rowheaderwidth

            ' split columns into page sets 
            Dim columnwidth As Single
            For i = 0 To colstoprint.Count - 1
                ' get initial column width 
                columnwidth = IIf((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) AndAlso i <> 0 Then
                    pagesets.Add(New PageDef(PrintMargins, colstoprint.Count))
                    pset += 1

                    ' Account for row headers 
                    pagesets(pset).coltotalwidth = rowheaderwidth
                End If

                ' 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
            Next

            '----------------------------------------------------------------- 
            ' Adjust column widths and table margins for each page 
            '----------------------------------------------------------------- 
            For i = 0 To pagesets.Count - 1
                AdjustPageSets(g, pagesets(i))
            Next
        End Sub

        ''' <summary> 
        ''' Adjust column widths for fixed and porportional columns, set the 
        ''' margins to enforce the selected tablealignment. 
        ''' </summary> 
        ''' <param name="g">The graphics context for all measurements</param> 
        ''' <param name="pageset">The pageset to adjust</param> 
        Private Sub AdjustPageSets(ByVal g As Graphics, ByVal pageset As PageDef)
            Dim i As Integer
            Dim fixedcolwidth As Single = rowheaderwidth
            Dim remainingcolwidth As Single = 0
            Dim ratio As Single

            '----------------------------------------------------------------- 
            ' Adjust the column widths in the page set to their final values, 
            ' accounting for overridden widths and porportional column stretching 
            '----------------------------------------------------------------- 

            ' calculate the amount of space reserved for fixed width columns 
            For i = 0 To pageset.colwidthsoverride.Count - 1
                If pageset.colwidthsoverride(i) >= 0 Then
                    fixedcolwidth += pageset.colwidthsoverride(i)
                End If
            Next

            ' calculate the amount space for non-overridden columns 
            For i = 0 To pageset.colwidths.Count - 1
                If pageset.colwidthsoverride(i) < 0 Then
                    remainingcolwidth += pageset.colwidths(i)
                End If
            Next

            ' calculate the ratio for porportional colums, use 1 for no 
            ' non-overridden columns or not porportional 
            If m_porportionalcolumns AndAlso 0 < remainingcolwidth Then
                ratio = (CSng(printWidth) - fixedcolwidth) / CSng(remainingcolwidth)
            Else
                ratio = CSng(1.0R)
            End If

            ' reset all column widths for override and/or porportionality. coltotalwidth 
            ' for each pageset should be <= pageWidth 
            pageset.coltotalwidth = rowheaderwidth
            For i = 0 To pageset.colwidths.Count - 1
                If pageset.colwidthsoverride(i) >= 0 Then
                    pageset.colwidths(i) = pageset.colwidthsoverride(i)
                Else
                    pageset.colwidths(i) = pageset.colwidths(i) * ratio
                End If

                pageset.coltotalwidth += pageset.colwidths(i)
            Next

            '----------------------------------------------------------------- 
            ' Table Alignment - now that we have the column widths established 
            ' we can reset the table margins to get left, right and centered 
            ' for the table on the page 
            '----------------------------------------------------------------- 

            ' Reset Print Margins based on table alignment 
            If Alignment.Left = m_tablealignment Then
                ' Bias table to the left by setting "right" value 
                pageset.margins.Right = pageWidth - pageset.margins.Left - CInt(pageset.coltotalwidth)
                If 0 > pageset.margins.Right Then
                    pageset.margins.Right = 0
                End If
            ElseIf Alignment.Right = m_tablealignment Then
                ' Bias table to the right by setting "left" value 
                pageset.margins.Left = pageWidth - pageset.margins.Right - CInt(pageset.coltotalwidth)
                If 0 > pageset.margins.Left Then
                    pageset.margins.Left = 0
                End If
            ElseIf Alignment.Center = m_tablealignment Then
                ' Bias the table to the center by setting left and right equal 
                pageset.margins.Left = (pageWidth - CInt(pageset.coltotalwidth)) / 2
                If 0 > pageset.margins.Left Then
                    pageset.margins.Left = 0
                End If
                pageset.margins.Right = pageset.margins.Left
            End If
        End Sub

        ''' <summary> 
        ''' Count the pages that would be printed if print all was selected 
        ''' </summary> 
        Private Function TotalPages() As Integer
            Dim pages As Integer = 1
            Dim pos As Single = 0
            Dim printablearea As Single = pageHeight - headerHeight - footerHeight - PrintMargins.Top - PrintMargins.Bottom

            For i As Integer = 0 To (rowheights.Count) - 1
                If pos + rowheights(i) > printablearea Then
                    ' count the page 
                    pages += 1
                    ' reset the counter 
                    pos = 0
                End If

                ' add space 
                pos += rowheights(i)
            Next

            Return pages
        End Function

        ''' <summary> 
        ''' Check for more pages. This is called at the end of printing a page set. 
        ''' If there's another page set to print, we return true. 
        ''' </summary> 
        Private Function DetermineHasMorePages() As Boolean
            currentpageset += 1
            If currentpageset < pagesets.Count Then
                'currentpageset--; // decrement back to a valid pageset number 
                ' tell the caller we're through. 
                Return True
            Else
                Return False
            End If
        End Function

        ''' <summary> 
        ''' This routine prints one page. It will skip non-printable pages if the user 
        ''' selected the "some pages" option on the print dialog. This is called during 
        ''' the Print event. 
        ''' </summary> 
        ''' <param name="g">Graphics object to print to</param> 
        Private Function PrintPage(ByVal g As Graphics) As Boolean
            ' flag for continuing or ending print process 
            Dim HasMorePages As Boolean = False

            ' flag for handling printing some pages rather than all 
            Dim printthispage As Boolean = False

            ' current printing position within one page 
            Dim printpos As Single = pagesets(currentpageset).margins.Top

            ' increment page number & check page range 
            CurrentPage += 1
            If (CurrentPage >= fromPage) AndAlso (CurrentPage <= toPage) Then
                printthispage = True
            End If

            ' calculate the static vertical space available - this is where we stop printing rows 
            Dim staticheight As Single = pageHeight - footerHeight - pagesets(currentpageset).margins.Bottom

            ' holder for one-row height lookahead to see if the row will fit on the page 
            Dim nextrowheight As Single

            '----------------------------------------------------------------- 
            ' scan down heights until we're off this (non-printing) page 
            '----------------------------------------------------------------- 

            While Not printthispage
                ' calculate and increment over the page we're not printing 
                printpos = pagesets(currentpageset).margins.Top + headerHeight

                ' are we done with this page? 
                Dim pagecomplete As Boolean = False

                ' do one row look-ahead to see if we have room on the page 
                nextrowheight = IIf((lastrowprinted < rowheights.Count), rowheights(lastrowprinted + 1), 0)
                While Not pagecomplete
                    ' this page is complete if we run out of data 
                    If lastrowprinted >= rowstoprint.Count - 1 Then
                        pagecomplete = True
                        ' ... or off the bottom of the page 
                    ElseIf (printpos + nextrowheight) >= staticheight Then
                        pagecomplete = True
                    Else
                        ' not done yet so consume space 
                        lastrowprinted += 1
                        printpos += rowheights(lastrowprinted)
                        nextrowheight = IIf((lastrowprinted + 1 < rowheights.Count), rowheights(lastrowprinted + 1), 0)
                    End If
                End While

                ' skip to the next page & see if it's in the print range 
                CurrentPage += 1
                If (CurrentPage >= fromPage) AndAlso (CurrentPage <= toPage) Then
                    printthispage = True
                End If

                ' bottom check~ out of data or out of pages 
                If (lastrowprinted >= rowstoprint.Count - 1) OrElse (CurrentPage > toPage) Then
                    ' reset for next pageset or tell the caller we're complete 
                    HasMorePages = DetermineHasMorePages()

                    ' reset counters since we'll go through this twice if we print from preview 
                    lastrowprinted = -1
                    CurrentPage = 0

                    Return HasMorePages
                End If
            End While

            '----------------------------------------------------------------- 
            ' print headers 
            '----------------------------------------------------------------- 

            ' reset printpos as it may have changed during the 'skip pages' routine just above. 
            printpos = pagesets(currentpageset).margins.Top

            ' Skip headers if the flag is false 
            If PrintHeader Then
                ' print page number if user selected it 
                If pagenumberontop Then
                    ' if we have a page number to print 
                    If pageno Then
                        Dim pagenumber As [String] = m_pagetext + CurrentPage.ToString(CultureInfo.CurrentCulture)
                        If m_showtotalpagenumber Then
                            pagenumber += m_pageseparator + _totalpages.ToString()
                        End If
                        If 1 < pagesets.Count Then
                            pagenumber += m_parttext + (currentpageset + 1).ToString(CultureInfo.CurrentCulture)
                        End If

                        ' ... then print it 
                        printsection(g, printpos, pagenumber, pagenofont, pagenocolor, m_pagenumberformat, _
                        overridepagenumberformat, pagesets(currentpageset).margins)

                        ' if the page number is not on a separate line, don't "use up" it's vertical space 
                        If Not m_pagenumberonseparateline Then
                            printpos -= pagenumberHeight
                        End If
                    End If
                End If

                ' print title if provided 
                If Not [String].IsNullOrEmpty(m_title) Then
                    printsection(g, printpos, m_title, m_titlefont, m_titlecolor, m_titleformat, _
                    overridetitleformat, pagesets(currentpageset).margins)
                End If

                ' print subtitle if provided 
                If Not [String].IsNullOrEmpty(m_subtitle) Then
                    printsection(g, printpos, m_subtitle, m_subtitlefont, m_subtitlecolor, m_subtitleformat, _
                    overridesubtitleformat, pagesets(currentpageset).margins)
                End If
            End If

            ' print the column headers or not based on our processing flag 
            If PrintColumnHeaders Then
                ' print column headers 
                PrintColumnHeader(g, printpos, pagesets(currentpageset))
            End If

            '----------------------------------------------------------------- 
            ' print rows until the page is complete 
            '----------------------------------------------------------------- 

            ' do one row look-ahead to see if we have room on the page 
            nextrowheight = IIf((lastrowprinted < rowheights.Count), rowheights(lastrowprinted + 1), 0)
            While (printpos + nextrowheight) < staticheight
                lastrowprinted += 1
                printrow(g, printpos, DirectCast((rowstoprint(lastrowprinted)), DataGridViewRow), pagesets(currentpageset))

                ' bottom check, we're really really done when there's no more data 
                If lastrowprinted >= rowstoprint.Count - 1 Then
                    ' print a footer for this page 
                    PrinterFooter(g, printpos, pagesets(currentpageset).margins)

                    ' check on more page sets or set no more pages flag 
                    HasMorePages = DetermineHasMorePages()

                    ' reset counters since we'll go through this twice if we print from preview 
                    lastrowprinted = -1
                    CurrentPage = 0

                    ' return 
                    Return HasMorePages
                Else
                    nextrowheight = IIf((lastrowprinted < rowheights.Count), rowheights(lastrowprinted + 1), 0)
                End If
            End While

            '----------------------------------------------------------------- 
            ' print footer 
            '----------------------------------------------------------------- 
            If PrintFooter Then
                PrinterFooter(g, printpos, pagesets(currentpageset).margins)
            End If

            '----------------------------------------------------------------- 
            ' bottom check, see if this is the last page to print 
            '----------------------------------------------------------------- 

            If CurrentPage >= toPage Then
                ' reset for next pageset or tell the caller we're complete 
                HasMorePages = DetermineHasMorePages()

                ' reset counters since we'll go through this twice if we print from preview 
                lastrowprinted = -1
                CurrentPage = 0
            Else
                ' we're not done yet 
                HasMorePages = True
            End If

            Return HasMorePages
        End Function

        ''' <summary> 
        ''' Print a header or footer section. Used for page numbers and titles 
        ''' </summary> 
        ''' <param name="g">Graphic context to print in</param> 
        ''' <param name="pos">Track vertical space used; 'y' location</param> 
        ''' <param name="text">String to print</param> 
        ''' <param name="font">Font to use for printing</param> 
        ''' <param name="color">Color to print in</param> 
        ''' <param name="format">String format for text</param> 
        ''' <param name="useroverride">True if the user overrode the alignment or flags</param> 
        ''' <param name="margins">The table's print margins</param> 
        Private Sub printsection(ByVal g As Graphics, ByRef pos As Single, ByVal text As String, ByVal font As Font, ByVal color As Color, ByVal format As StringFormat, _
        ByVal useroverride As Boolean, ByVal margins As Margins)
            ' measure string 
            Dim printsize As SizeF = g.MeasureString(text, font, printWidth, format)

            ' build area to print within 
            Dim printarea As New RectangleF(CSng(margins.Left), pos, CSng(printWidth), printsize.Height)

            ' do the actual print 
            g.DrawString(text, font, New SolidBrush(color), printarea, format)

            ' track "used" vertical space 
            pos += printsize.Height
        End Sub

        ''' <summary> 
        ''' Print the footer. This handles the footer spacing, and printing the page number 
        ''' at the bottom of the page (if the page number is not in the header). 
        ''' </summary> 
        ''' <param name="g">Graphic context to print in</param> 
        ''' <param name="pos">Track vertical space used; 'y' location</param> 
        ''' <param name="margins">The table's print margins</param> 
        Private Sub PrinterFooter(ByVal g As Graphics, ByRef pos As Single, ByVal margins As Margins)
            ' print last footer. Note: need to force printpos to the bottom of the page 
            ' as we may have run out of data anywhere on the page 
            pos = pageHeight - footerHeight - margins.Bottom
            ' - margins.Top 
            ' add spacing 
            pos += m_footerspacing

            ' print the footer 
            printsection(g, pos, m_footer, m_footerfont, m_footercolor, m_footerformat, _
            overridefooterformat, margins)

            ' print the page number if it's on the bottom. 
            If Not pagenumberontop Then
                If pageno Then
                    Dim pagenumber As [String] = m_pagetext + CurrentPage.ToString(CultureInfo.CurrentCulture)
                    If m_showtotalpagenumber Then
                        pagenumber += m_pageseparator + _totalpages.ToString()
                    End If
                    If 1 < pagesets.Count Then
                        pagenumber += m_parttext + (currentpageset + 1).ToString(CultureInfo.CurrentCulture)
                    End If

                    ' if the pageno is not on a separate line, push the print location up by its height. 
                    If Not m_pagenumberonseparateline Then
                        pos = pos - pagenumberHeight
                    End If

                    ' print the page number 
                    printsection(g, pos, pagenumber, pagenofont, pagenocolor, m_pagenumberformat, _
                    overridepagenumberformat, margins)
                End If
            End If
        End Sub

        ''' <summary> 
        ''' Print the column headers. Most printing format info is retrieved from the 
        ''' source DataGridView. 
        ''' </summary> 
        ''' <param name="g">Graphics Context to print within</param> 
        ''' <param name="pos">Track vertical space used; 'y' location</param> 
        ''' <param name="pageset">Current pageset - defines columns and margins</param> 
        Private Sub PrintColumnHeader(ByVal g As Graphics, ByRef pos As Single, ByVal pageset As PageDef)
            ' track printing location accross the page. start position is hard left, 
            ' adjusted for the row headers. Note rowheaderwidth is 0 if row headers are not printed 
            Dim xcoord As Single = pageset.margins.Left + rowheaderwidth

            ' set the pen for drawing the grid lines 
            Dim lines As New Pen(dgv.GridColor, 1)

            '----------------------------------------------------------------- 
            ' Print the column headers 
            '----------------------------------------------------------------- 
            Dim col As DataGridViewColumn
            For i As Integer = 0 To pageset.colstoprint.Count - 1
                col = DirectCast(pageset.colstoprint(i), DataGridViewColumn)

                ' calc cell width, account for columns larger than the print area! 
                Dim cellwidth As Single = (IIf(pageset.colwidths(i) > printWidth - rowheaderwidth, printWidth - rowheaderwidth, pageset.colwidths(i)))

                ' get column style 
                Dim style As DataGridViewCellStyle = col.HeaderCell.InheritedStyle

                ' set print area for this individual cell, account for cells larger 
                ' than the print area! 
                Dim cellprintarea As New RectangleF(xcoord, pos, cellwidth, colheaderheight)

                ' print column header background 
                g.FillRectangle(New SolidBrush(style.BackColor), cellprintarea)

                ' draw the text 
                g.DrawString(col.HeaderText, style.Font, New SolidBrush(style.ForeColor), cellprintarea, headercellformat)

                ' draw the borders - default to the dgv's border setting; account for 
                ' columns larger than the print width 
                If dgv.ColumnHeadersBorderStyle <> DataGridViewHeaderBorderStyle.None Then
                    g.DrawRectangle(lines, xcoord, pos, cellwidth, colheaderheight)
                End If

                xcoord += pageset.colwidths(i)
            Next

            ' all done, consume "used" vertical space, including space for border lines 
            pos += colheaderheight + (IIf(dgv.ColumnHeadersBorderStyle <> DataGridViewHeaderBorderStyle.None, lines.Width, 0))
        End Sub

        ''' <summary> 
        ''' Print one row of the DataGridView. Most printing format info is retrieved 
        ''' from the DataGridView. 
        ''' </summary> 
        ''' <param name="g">Graphics Context to print within</param> 
        ''' <param name="pos">Track vertical space used; 'y' location</param> 
        ''' <param name="row">The row that will be printed</param> 
        ''' <param name="pageset">Current Pageset - defines columns and margins</param> 
        Private Sub printrow(ByVal g As Graphics, ByRef pos As Single, ByVal row As DataGridViewRow, ByVal pageset As PageDef)
            ' track printing location accross the page 
            Dim xcoord As Single = pageset.margins.Left

            ' set the pen for drawing the grid lines 
            Dim lines As New Pen(dgv.GridColor, 1)

            '----------------------------------------------------------------- 
            ' Print Row background 
            '----------------------------------------------------------------- 

            ' get current row style, start with header style 
            Dim rowstyle As DataGridViewCellStyle = row.InheritedStyle

            ' calc row width, account for columns larger than the print area! 
            Dim rowwidth As Single = (IIf(pageset.coltotalwidth > printWidth, printWidth, pageset.coltotalwidth))

            ' define print rectangle 
            Dim printarea As New RectangleF(xcoord, pos, rowwidth, rowheights(lastrowprinted))

            ' fill in the row background as the default color 
            g.FillRectangle(New SolidBrush(rowstyle.BackColor), printarea)

            '----------------------------------------------------------------- 
            ' Print the Row Headers, if they are visible 
            '----------------------------------------------------------------- 
            If dgv.RowHeadersVisible Then
                ' get current row style, start with header style 
                Dim headerstyle As DataGridViewCellStyle = row.HeaderCell.InheritedStyle

                ' set print area for this individual cell 
                Dim headercellprintarea As New RectangleF(xcoord, pos, rowheaderwidth, rowheights(lastrowprinted))

                ' fill in the row header background 
                g.FillRectangle(New SolidBrush(headerstyle.BackColor), headercellprintarea)

                ' draw the text for the row header cell 
                g.DrawString(row.HeaderCell.EditedFormattedValue.ToString(), headerstyle.Font, New SolidBrush(headerstyle.ForeColor), headercellprintarea, headercellformat)

                ' draw the borders - default to the dgv's border setting 
                If dgv.RowHeadersBorderStyle <> DataGridViewHeaderBorderStyle.None Then
                    g.DrawRectangle(lines, xcoord, pos, rowheaderwidth, rowheights(lastrowprinted))
                End If

                ' track horizontal space used 
                xcoord += rowheaderwidth
            End If

            '----------------------------------------------------------------- 
            ' Print the row: write and draw each cell 
            '----------------------------------------------------------------- 
            Dim col As DataGridViewColumn
            For i As Integer = 0 To pageset.colstoprint.Count - 1
                ' access the column being printed 
                col = DirectCast(pageset.colstoprint(i), DataGridViewColumn)

                ' access the data to be printed - weird bug: had to move this up here since 
                ' doing this access actually changes the cell's style. ??? 
                Dim datastr As [String] = row.Cells(col.Index).EditedFormattedValue.ToString()

                ' calc cell width, account for columns larger than the print area! 
                Dim cellwidth As Single = (IIf(pageset.colwidths(i) > printWidth - rowheaderwidth, printWidth - rowheaderwidth, pageset.colwidths(i)))

                ' get DGV column style and see if we have an override for this column 
                Dim finalformat As StringFormat = Nothing
                Dim cellfont As Font = Nothing
                Dim colstyle As DataGridViewCellStyle = Nothing
                If ColumnStyles.ContainsKey(col.Name) Then
                    colstyle = colstyles(col.Name)

                    ' set string format 
                    buildstringformat(finalformat, colstyle, cellformat.Alignment, cellformat.LineAlignment, cellformat.FormatFlags, cellformat.Trimming)
                    cellfont = colstyle.Font
                ElseIf (col.HasDefaultCellStyle) OrElse (row.Cells(col.Index).HasStyle) Then
                    colstyle = row.Cells(col.Index).InheritedStyle

                    ' set string format 
                    buildstringformat(finalformat, colstyle, cellformat.Alignment, cellformat.LineAlignment, cellformat.FormatFlags, cellformat.Trimming)
                    cellfont = colstyle.Font
                Else
                    finalformat = cellformat

                    ' inherited style == default style (mostly) if no style was ever set. 
                    colstyle = row.Cells(col.Index).InheritedStyle
                End If

                ' set print area for this individual cell 
                Dim cellprintarea As New RectangleF(xcoord, pos, cellwidth, rowheights(lastrowprinted))

                ' fill in the cell background - using the selected style 
                g.FillRectangle(New SolidBrush(colstyle.BackColor), cellprintarea)

                ' draw content based on cell style 
                If "DataGridViewImageCell" = col.CellType.Name Then
                    DrawImageCell(g, DirectCast(row.Cells(col.Index), DataGridViewImageCell), cellprintarea)
                Else
                    ' draw the text for the cell at the row / col intersection 
                    g.DrawString(datastr, colstyle.Font, New SolidBrush(colstyle.ForeColor), cellprintarea, finalformat)
                End If

                ' draw the borders - default to the dgv's border setting 
                If dgv.CellBorderStyle <> DataGridViewCellBorderStyle.None Then
                    g.DrawRectangle(lines, xcoord, pos, cellwidth, rowheights(lastrowprinted))
                End If

                ' track horizontal space used 
                xcoord += pageset.colwidths(i)
            Next

            '----------------------------------------------------------------- 
            ' All done with this row, consume "used" vertical space 
            '----------------------------------------------------------------- 
            pos += rowheights(lastrowprinted)
        End Sub

        ''' <summary>
        ''' Draws the image cell.
        ''' </summary>
        ''' <param name="g">The g.</param>
        ''' <param name="imagecell">The imagecell.</param>
        ''' <param name="rectf">The rectf.</param>
        Private Sub DrawImageCell(ByVal g As Graphics, ByVal imagecell As DataGridViewImageCell, ByVal rectf As RectangleF)
            ' image to draw 
            Dim img As Image = DirectCast(imagecell.Value, System.Drawing.Image)

            ' clipping bounds. This is the portion of the image to fit into the drawing rectangle 
            Dim src As New Rectangle()

            ' calculate deltas 
            Dim dx As Integer = 0
            Dim dy As Integer = 0

            ' drawn normal size, clipped to cell 
            If (DataGridViewImageCellLayout.Normal = imagecell.ImageLayout) OrElse (DataGridViewImageCellLayout.NotSet = imagecell.ImageLayout) Then
                ' calculate origin deltas, used to move image 
                dx = img.Width - CInt(rectf.Width)
                dy = img.Height - CInt(rectf.Height)

                ' set destination width and height to clip to cell 
                If 0 > dx Then
                    rectf.Width = InlineAssignHelper(src.Width, img.Width)
                Else
                    src.Width = CInt(rectf.Width)
                End If
                If 0 > dy Then
                    rectf.Height = InlineAssignHelper(src.Height, img.Height)
                Else
                    src.Height = CInt(rectf.Height)

                End If
            ElseIf DataGridViewImageCellLayout.Stretch = imagecell.ImageLayout Then
                ' stretch image to fit cell size 
                src.Width = img.Width
                src.Height = img.Height

                ' change the origin delta's to 0 so we don't move the image 
                dx = 0
                dy = 0
            Else
                ' DataGridViewImageCellLayout.Zoom 
                ' scale image to fit in cell 
                src.Width = img.Width
                src.Height = img.Height

                Dim vertscale As Single = rectf.Height / src.Height
                Dim horzscale As Single = rectf.Width / src.Width
                Dim scale As Single

                ' use the smaller scaling factor to ensure the image will fit in the cell 
                If vertscale > horzscale Then
                    ' use horizontal scale, don't move image horizontally 
                    scale = horzscale
                    dx = 0
                    dy = CInt(((src.Height * scale) - rectf.Height))
                Else
                    ' use vertical scale, don't move image vertically 
                    scale = vertscale
                    dy = 0
                    dx = CInt(((src.Width * scale) - rectf.Width))
                End If

                ' set target size to match scaled image 
                rectf.Width = src.Width * scale
                rectf.Height = src.Height * scale
            End If

            'calculate image drawing origin based on origin deltas 
            Select Case imagecell.InheritedStyle.Alignment
                Case DataGridViewContentAlignment.BottomCenter
                    If 0 > dy Then
                        rectf.Y -= dy
                    Else
                        src.Y = dy
                    End If
                    If 0 > dx Then
                        rectf.X -= dx / 2
                    Else
                        src.X = dx / 2
                    End If
                    Exit Select
                Case DataGridViewContentAlignment.BottomLeft
                    If 0 > dy Then
                        rectf.Y -= dy
                    Else
                        src.Y = dy
                    End If
                    src.X = 0
                    Exit Select
                Case DataGridViewContentAlignment.BottomRight
                    If 0 > dy Then
                        rectf.Y -= dy
                    Else
                        src.Y = dy
                    End If
                    If 0 > dx Then
                        rectf.X -= dx
                    Else
                        src.X = dx
                    End If
                    Exit Select
                Case DataGridViewContentAlignment.MiddleCenter
                    If 0 > dy Then
                        rectf.Y -= dy / 2
                    Else
                        src.Y = dy / 2
                    End If
                    If 0 > dx Then
                        rectf.X -= dx / 2
                    Else
                        src.X = dx / 2
                    End If
                    Exit Select
                Case DataGridViewContentAlignment.MiddleLeft
                    If 0 > dy Then
                        rectf.Y -= dy / 2
                    Else
                        src.Y = dy / 2
                    End If
                    src.X = 0
                    Exit Select
                Case DataGridViewContentAlignment.MiddleRight
                    If 0 > dy Then
                        rectf.Y -= dy / 2
                    Else
                        src.Y = dy / 2
                    End If
                    If 0 > dx Then
                        rectf.X -= dx
                    Else
                        src.X = dx
                    End If
                    Exit Select
                Case DataGridViewContentAlignment.TopCenter
                    src.Y = 0
                    If 0 > dx Then
                        rectf.X -= dx / 2
                    Else
                        src.X = dx / 2
                    End If
                    Exit Select
                Case DataGridViewContentAlignment.TopLeft
                    src.Y = 0
                    src.X = 0
                    Exit Select
                Case DataGridViewContentAlignment.TopRight
                    src.Y = 0
                    If 0 > dx Then
                        rectf.X -= dx
                    Else
                        src.X = dx
                    End If
                    Exit Select
                Case DataGridViewContentAlignment.NotSet
                    If 0 > dy Then
                        rectf.Y -= dy / 2
                    Else
                        src.Y = dy / 2
                    End If
                    If 0 > dx Then
                        rectf.X -= dx / 2
                    Else
                        src.X = dx / 2
                    End If
                    Exit Select
            End Select

            ' Now we can draw our image 
            g.DrawImage(img, rectf, src, GraphicsUnit.Pixel)
        End Sub
        Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
            target = value
            Return value
        End Function
    End Class
End Namespace


modified on Tuesday, November 24, 2009 6:25 PM

GeneralRe: Problem in dll - VB CODE FOR YOU Pin
aureolin24-Nov-09 10:23
aureolin24-Nov-09 10:23 
GeneralLogo propertie Pin
VOPhil6-Oct-09 3:27
VOPhil6-Oct-09 3:27 
GeneralRe: Logo propertie Pin
aureolin6-Oct-09 10:39
aureolin6-Oct-09 10:39 
GeneralRe: Logo propertie Pin
Laan824-Nov-09 22:47
Laan824-Nov-09 22:47 
QuestionHow to also print a cell drawn by me? Pin
fishcosine7-Sep-09 10:07
fishcosine7-Sep-09 10:07 
AnswerRe: How to also print a cell drawn by me? Pin
aureolin8-Sep-09 7:15
aureolin8-Sep-09 7:15 
GeneralRe: How to also print a cell drawn by me? Pin
fishcosine8-Sep-09 7:45
fishcosine8-Sep-09 7:45 
GeneralRe: How to also print a cell drawn by me? Pin
aureolin8-Sep-09 9:35
aureolin8-Sep-09 9:35 
Generalsource code did not update Pin
Huisheng Chen4-Sep-09 15:13
Huisheng Chen4-Sep-09 15:13 
GeneralRe: source code did not update Pin
aureolin4-Sep-09 17:50
aureolin4-Sep-09 17:50 
GeneralArticle incomplete in version 10 Pin
LittleBlueBird2-Sep-09 22:59
LittleBlueBird2-Sep-09 22:59 
GeneralRe: Article incomplete in version 10 Pin
aureolin3-Sep-09 7:38
aureolin3-Sep-09 7:38 
Generalprinter for windows form which has other information and datagridview Pin
lang21-Jul-09 21:47
lang21-Jul-09 21:47 
GeneralRe: printer for windows form which has other information and datagridview Pin
aureolin22-Jul-09 14:00
aureolin22-Jul-09 14:00 
GeneralRe: printer for windows form which has other information and datagridview Pin
lang22-Jul-09 18:26
lang22-Jul-09 18:26 
GeneralRe: printer for windows form which has other information and datagridview Pin
aureolin23-Jul-09 5:05
aureolin23-Jul-09 5:05 
GeneralRe: printer for windows form which has other information and datagridview Pin
lang23-Jul-09 11:50
lang23-Jul-09 11:50 

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.