DataGridView Printing




Simple Multi-Page Printing

The example shows how to print text and a multiple page DataGridView using a PrintDocument Component and GDI+.
It also shows how to preview your printing in a System.Windows.Forms .PrintPreviewDialog.

This is designed and written dynamically, i.e. it's not a one time code, it's versatile and can be used for any multi (printed) page DataGridView. Try resizing the Columns or Rows and you'll see what I mean.

The majority of the code in this example is for calculating printed page ranges for printing the DataGridView. The actual printing is very simple, as it uses standard GDI+ techniques in the PrintDocument1_PrintPage event (System.Drawing.Printing.PrintDocument).

The Graphics Device Interface (GDI) is a Microsoft Windows application programming interface and core operating system component responsible for representing graphical objects and transmitting them to output devices such as monitors and printers.


GDI is responsible for tasks such as drawing lines and curves, rendering fonts and handling palettes.


The source code for this example contains two notable methods... the PrintDocument_BeginPrint event and the PrintDocument_PrintPage event.

The ranges to be printed are measured and recorded in the PrintDocument_BeginPrint event, and the data recorded is then used in the PrintDocument_PrintPage event where the Label and DGV to be printed is rendered onto your printed page.



BeginPrint

''' <summary>
    ''' the majority of this Sub is calculating printed page ranges
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub PrintDocument1_BeginPrint(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintEventArgs) Handles PrintDocument1.BeginPrint
        ''this removes the printed page margins
        PrintDocument1.OriginAtMargins = True
        PrintDocument1.DefaultPageSettings.Margins = New Drawing.Printing.Margins(0, 0, 0, 0)

        pages = New Dictionary(Of Integer, pageDetails)

        Dim maxWidth As Integer
        Dim maxHeight As Integer

        If PrintDocument1.DefaultPageSettings.Landscape = False Then
            maxWidth = CInt(PrintDocument1.DefaultPageSettings.PrintableArea.Width) - 120
            maxHeight = CInt(PrintDocument1.DefaultPageSettings.PrintableArea.Height) - 120 + Label1.Height
        Else
            maxWidth = CInt(PrintDocument1.DefaultPageSettings.PrintableArea.Height) - 40
            maxHeight = CInt(PrintDocument1.DefaultPageSettings.PrintableArea.Width) - 150 + Label1.Height
        End If

        Dim pageCounter As Integer = 0
        pages.Add(pageCounter, New pageDetails)

        Dim columnCounter As Integer = 0

        Dim columnSum As Integer = DataGridView1.RowHeadersWidth

        For c As Integer = 0 To DataGridView1.Columns.Count - 1
            If columnSum + DataGridView1.Columns(c).Width < maxWidth Then
                columnSum += DataGridView1.Columns(c).Width
                columnCounter += 1
            Else
                pages(pageCounter) = New pageDetails With {.columns = columnCounter, .rows = 0, .startCol = pages(pageCounter).startCol}
                columnSum = DataGridView1.RowHeadersWidth + DataGridView1.Columns(c).Width
                columnCounter = 1
                pageCounter += 1
                pages.Add(pageCounter, New pageDetails With {.startCol = c})
            End If
            If c = DataGridView1.Columns.Count - 1 Then
                If pages(pageCounter).columns = 0 Then
                    pages(pageCounter) = New pageDetails With {.columns = columnCounter, .rows = 0, .startCol = pages(pageCounter).startCol}
                End If
            End If
        Next

        maxPagesWide = pages.Keys.Max + 1

        pageCounter = 0

        Dim rowCounter As Integer = 0

        Dim rowSum As Integer = DataGridView1.ColumnHeadersHeight

        For r As Integer = 0 To DataGridView1.Rows.Count - 2
            If rowSum + DataGridView1.Rows(r).Height < maxHeight Then
                rowSum += DataGridView1.Rows(r).Height
                rowCounter += 1
            Else
                pages(pageCounter) = New pageDetails With {.columns = pages(pageCounter).columns, .rows = rowCounter, .startCol = pages(pageCounter).startCol, .startRow = pages(pageCounter).startRow}
                For x As Integer = 1 To maxPagesWide - 1
                    pages(pageCounter + x) = New pageDetails With {.columns = pages(pageCounter + x).columns, .rows = rowCounter, .startCol = pages(pageCounter + x).startCol, .startRow = pages(pageCounter).startRow}
                Next

                pageCounter += maxPagesWide
                For x As Integer = 0 To maxPagesWide - 1
                    pages.Add(pageCounter + x, New pageDetails With {.columns = pages(x).columns, .rows = 0, .startCol = pages(x).startCol, .startRow = r})
                Next

                rowSum = DataGridView1.ColumnHeadersHeight + DataGridView1.Rows(r).Height
                rowCounter = 1
            End If
            If r = DataGridView1.Rows.Count - 2 Then
                For x As Integer = 0 To maxPagesWide - 1
                    If pages(pageCounter + x).rows = 0 Then
                        pages(pageCounter + x) = New pageDetails With {.columns = pages(pageCounter + x).columns, .rows = rowCounter, .startCol = pages(pageCounter + x).startCol, .startRow = pages(pageCounter + x).startRow}
                    End If
                Next
            End If
        Next

        maxPagesTall = pages.Count \ maxPagesWide

    End Sub




PrintPage

''' <summary>
    ''' this is the actual printing routine.
    ''' using the pagedetails i calculated earlier, it prints a title,
    ''' + as much of the datagridview as will fit on 1 page, then moves to the next page.
    ''' this is setup to be dynamic. try resizing the dgv columns or rows
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
        Dim rect As New Rectangle(20, 20, CInt(PrintDocument1.DefaultPageSettings.PrintableArea.Width), Label1.Height)
        Dim sf As New StringFormat
        sf.Alignment = StringAlignment.Center
        sf.LineAlignment = StringAlignment.Center
        'truncate text overflow
        'sf.Trimming = StringTrimming.EllipsisCharacter
        'sf.FormatFlags = StringFormatFlags.NoWrap

        e.Graphics.DrawString(Label1.Text, Label1.Font, Brushes.Black, rect, sf)

        sf.Alignment = StringAlignment.Near

        Dim startX As Integer = 50
        Dim startY As Integer = rect.Bottom

        Static startPage As Integer = 0

        For p As Integer = startPage To pages.Count - 1
            Dim cell As New Rectangle(startX, startY, DataGridView1.RowHeadersWidth, DataGridView1.ColumnHeadersHeight)
            e.Graphics.FillRectangle(New SolidBrush(SystemColors.ControlLight), cell)
            e.Graphics.DrawRectangle(Pens.Black, cell)

            startY += DataGridView1.ColumnHeadersHeight

            For r As Integer = pages(p).startRow To pages(p).startRow + pages(p).rows - 1
                cell = New Rectangle(startX, startY, DataGridView1.RowHeadersWidth, DataGridView1.Rows(r).Height)
                e.Graphics.FillRectangle(New SolidBrush(SystemColors.ControlLight), cell)
                e.Graphics.DrawRectangle(Pens.Black, cell)
                e.Graphics.DrawString(DataGridView1.Rows(r).HeaderCell.Value.ToString, DataGridView1.Font, Brushes.Black, cell, sf)
                startY += DataGridView1.Rows(r).Height
            Next

            startX += cell.Width
            startY = rect.Bottom

            For c As Integer = pages(p).startCol To pages(p).startCol + pages(p).columns - 1
                cell = New Rectangle(startX, startY, DataGridView1.Columns(c).Width, DataGridView1.ColumnHeadersHeight)
                e.Graphics.FillRectangle(New SolidBrush(SystemColors.ControlLight), cell)
                e.Graphics.DrawRectangle(Pens.Black, cell)
                e.Graphics.DrawString(DataGridView1.Columns(c).HeaderCell.Value.ToString, DataGridView1.Font, Brushes.Black, cell, sf)
                startX += DataGridView1.Columns(c).Width
            Next

            startY = rect.Bottom + DataGridView1.ColumnHeadersHeight

            For r As Integer = pages(p).startRow To pages(p).startRow + pages(p).rows - 1
                startX = 50 + DataGridView1.RowHeadersWidth
                For c As Integer = pages(p).startCol To pages(p).startCol + pages(p).columns - 1
                    cell = New Rectangle(startX, startY, DataGridView1.Columns(c).Width, DataGridView1.Rows(r).Height)
                    e.Graphics.DrawRectangle(Pens.Black, cell)
                    e.Graphics.DrawString(DataGridView1(c, r).Value.ToString, DataGridView1.Font, Brushes.Black, cell, sf)
                    startX += DataGridView1.Columns(c).Width
                Next
                startY += DataGridView1.Rows(r).Height
            Next

            If p <> pages.Count - 1 Then
                startPage = p + 1
                e.HasMorePages = True
                Return
            Else
                startPage = 0
            End If

        Next

    End Sub



Simple Multi-Page DataGridView Printing

Simple Multi-Page DataGridView Printing (Landscape)






Advanced Multi-Page Printing

The standard .Net DataGridView is a fairly versatile customizable control, that allows viewing and editing tables of data which can be either bound or unbound. The ExtendedDataGridView adds some extra functionality to the standard .Net DataGridView making it easier to use in the case of the extra Events, and the printing and exporting as CSV, further enhance the control.

In a simple application that binds data to the DataGridView, it's slightly more difficult to use the ExtendedDataGridView, but not impossible by any means. The issue is, if you allow the DataGridView to AutogenerateColumns, it will use the standard DataGridViewColumns. Fortunately changing column types in code is a simple task, or you can set AutogenerateColumns to False and bind your columns manually.

This is an extended DataGridView control that adds three public methods, a ContextMenuStrip, and two custom Events to a standard DataGridView control.

The three methods are:


with the purposes being fairly self-explanatory.

The ContextMenuStrip has MenuItems for invoking those three methods and a Reveal MenuItem which hosts a UserControl used for revealing hidden columns.
The (DataGridView ) control uses custom columns which provide an extra property to each extended version of the standard DataGridView columns, allowing selection of two custom HeaderCells for the extended DataGridViewCheckBoxColumn, which are:


along with:


Again the purposes of the HeaderCells are self-explanatory.

All of the other extended DataGridViewColumns don't have the CheckAll option.

The Printer class called by the Print method is optimized to print any combination of columns and rows and also prints visual elements, such as ComboBoxes, CheckBoxes, Images, Links, and Cell backcolors.
Both the Print and the PreviewPrint methods use a custom PrintDialog, which allows selection of a printer, page ranges (FromPage, ToPage ), page orientation, number of copies and collating pages in multiple copy print runs.

The SaveasCSV option allows exporting the DataGridView contents as a CSV file.

The custom Events provided by the extended DataGridView are:

cellCheckedChanged - which exposes these properties in the DataGridViewCheckBoxCell:


cellSelectedIndexChanged - which exposes these properties in the DataGridViewComboBoxCell:




















The Extended Columns - An Example

Imports System.ComponentModel
 
Public Class altDataGridViewCheckBoxColumn
    Inherits DataGridViewCheckBoxColumn
 
    Private _HeaderStyle As enumerations.style2
    <Category("Design"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)>
    Public Property HeaderStyle() As enumerations.style2
        Get
            Return _HeaderStyle
        End Get
        Set(ByVal value As enumerations.style2)
            _HeaderStyle = value
            Select Case _HeaderStyle
                Case style2.Standard
                    MyBase.HeaderCell = New DataGridViewColumnHeaderCell
                Case style2.CheckAll
                    MyBase.HeaderCell = New checkAllHeaderCell
                Case style2.HideColumn
                    MyBase.HeaderCell = New checkHideColumnHeaderCell
            End Select
        End Set
    End Property
 
    Public Overrides Function clone() As Object
        Dim copyColumn As altDataGridViewCheckBoxColumn = DirectCast(MyBase.Clone, altDataGridViewCheckBoxColumn)
        copyColumn._HeaderStyle = Me.HeaderStyle
        Return copyColumn
    End Function
 
End Class



Overall, the ExtendedDataGridView provides a simpler experience for the developer. Two issues that are queried regularly are how to capture CheckedChanged events and how to get more information about the ComboBox selection in a DataGridView. The ExtendedDataGridView simplifies both of these issues by providing two custom Events. Another two issues regularly asked are how to add a CheckBox to a HeaderCell. This is another addition included in the ExtendedDataGridView. DataGridViewCheckBoxColumns HeaderCells allow CheckAll CheckBoxes or HideColumn CheckBoxes, and all of the other extended column types allow just HideColumn CheckBoxes in the HeaderCell. Also, Printing is a major hurdle for a developer utilizing a DataGridView. In the ExtendedDataGridView control, that is a built-in feature. To print your DataGridView, you just need to call it's Print() method. Similarly, you can preview your printing by calling the PreviewPrint() method.
Finally, you can also export to CSV by calling the SaveasCSV() method.
These additional features enable faster application development without leaving out valuable functionality...



Advanced Multi-Page DataGridView Printing