Search code examples
.netvb.netdatagridviewprintdocument

How to print datagridview table with its header in vb.net?


I'm creating a print preview function in a system that I'm developing which will preview the datagridview that I want to print. I used ooopsoft's codes as reference and it works fine except for a slight problem.

Problem:

enter image description here

In the you can see that the dgv row with serial number 1 is missing. It appears the header has overwritten the 1st row. I have tried a myriad of ways to solve it, but I still can't find the solution. I tried exiting the print preview dialog and opening it again, but this is the result I got. I think I'm missing a line of code, but I can't figure out what. Please help.


Solution

  • The original code is a nice start but has a couple of bugs and inefficiecies:

    • It uses the newpage flag to print the header or the first row when there is a new page. Obviously you really want it to do both
    • Printing the column headers is done once per page, so it doesnt need to be in the data print loop at all
    • It is not allowing for invisible columns or columns with other than default alignment, There could be other such settings you want to account for.
    • Because it is not actually printing the correct number of rows, once you fix that you'll find that it reprints the last row from the previous page as the first row of a new page.
    • There is an internal gutter or margin so that text does not print too close to gridlines - this just uses an offset of 1 or 2
    • It is also needlessly using single and RectangleF
    • It is also not prepared for the Document to be shown again or Printed. You will also want to reset mRow and newpage either in the button click or BeginPrint event.

    I added a few comments as well as coloring the header row and demonstrating how to implement things like a RowPrePaint rule.

    Private mRow As Integer = 0
    Private newpage As Boolean = True
    
    Private Sub PrintDocument1_PrintPage(sender As System.Object,
                        e As PrintPageEventArgs) Handles PrintDocument1.PrintPage
    
        ' sets it to show '...' for long text
        Dim fmt As StringFormat = New StringFormat(StringFormatFlags.LineLimit)
        fmt.LineAlignment = StringAlignment.Center
        fmt.Trimming = StringTrimming.EllipsisCharacter
        Dim y As Int32 = e.MarginBounds.Top
        Dim rc As Rectangle
        Dim x As Int32
        Dim h As Int32 = 0
        Dim row As DataGridViewRow
    
        ' print the header text for a new page
        '   use a grey bg just like the control
        If newpage Then
            row = dgvZZ.Rows(mRow)
            x = e.MarginBounds.Left
            For Each cell As DataGridViewCell In row.Cells
                ' since we are printing the control's view,
                ' skip invidible columns
                If cell.Visible Then
                    rc = New Rectangle(x, y, cell.Size.Width, cell.Size.Height)
    
                    e.Graphics.FillRectangle(Brushes.LightGray, rc)
                    e.Graphics.DrawRectangle(Pens.Black, rc)
    
                    ' reused in the data pront - should be a function
                    Select Case dgvZZ.Columns(cell.ColumnIndex).DefaultCellStyle.Alignment
                        Case DataGridViewContentAlignment.BottomRight,
                             DataGridViewContentAlignment.MiddleRight
                            fmt.Alignment = StringAlignment.Far
                            rc.Offset(-1, 0)
                        Case DataGridViewContentAlignment.BottomCenter,
                            DataGridViewContentAlignment.MiddleCenter
                            fmt.Alignment = StringAlignment.Center
                        Case Else
                            fmt.Alignment = StringAlignment.Near
                            rc.Offset(2, 0)
                    End Select
    
                    e.Graphics.DrawString(dgvZZ.Columns(cell.ColumnIndex).HeaderText,
                                                dgvZZ.Font, Brushes.Black, rc, fmt)
                    x += rc.Width
                    h = Math.Max(h, rc.Height)
                End If
            Next
            y += h
    
        End If
        newpage = False
    
        ' now print the data for each row
        Dim thisNDX As Int32
        For thisNDX = mRow To dgvZZ.RowCount - 1
            ' no need to try to print the new row
            If dgvZZ.Rows(thisNDX).IsNewRow Then Exit For
    
            row = dgvZZ.Rows(thisNDX)
            x = e.MarginBounds.Left
            h = 0
    
            ' reset X for data
            x = e.MarginBounds.Left
    
            ' print the data
            For Each cell As DataGridViewCell In row.Cells
                If cell.Visible Then
                    rc = New Rectangle(x, y, cell.Size.Width, cell.Size.Height)
    
                    ' SAMPLE CODE: How To 
                    ' up a RowPrePaint rule
                    'If Convert.ToDecimal(row.Cells(5).Value) < 9.99 Then
                    '    Using br As New SolidBrush(Color.MistyRose)
                    '        e.Graphics.FillRectangle(br, rc)
                    '    End Using
                    'End If
    
                    e.Graphics.DrawRectangle(Pens.Black, rc)
    
                    Select Case dgvZZ.Columns(cell.ColumnIndex).DefaultCellStyle.Alignment
                        Case DataGridViewContentAlignment.BottomRight,
                             DataGridViewContentAlignment.MiddleRight
                            fmt.Alignment = StringAlignment.Far
                            rc.Offset(-1, 0)
                        Case DataGridViewContentAlignment.BottomCenter,
                            DataGridViewContentAlignment.MiddleCenter
                            fmt.Alignment = StringAlignment.Center
                        Case Else
                            fmt.Alignment = StringAlignment.Near
                            rc.Offset(2, 0)
                    End Select
    
                    e.Graphics.DrawString(cell.FormattedValue.ToString(),
                                          dgvZZ.Font, Brushes.Black, rc, fmt)
    
                    x += rc.Width
                    h = Math.Max(h, rc.Height)
                End If
    
            Next
            y += h
            ' next row to print
            mRow = thisNDX + 1
    
            If y + h > e.MarginBounds.Bottom Then
                e.HasMorePages = True
                ' mRow -= 1   causes last row to rePrint on next page
                newpage = True
                Return
            End If
        Next
    
    
    End Sub
    

    enter image description hereenter image description here

    Note that there is an Id column set to invisible in the DGV, the Color column is centered and Price is left justified - these are all settings picked up from the control. Also note that the text is moved away from the gridlines just a bit.


    The last bullet point above, You will also want to reset mRow and newpage either in the button click or BeginPrint event. means this:

    Private Sub PrintDocument1_BeginPrint(sender As Object, 
              e As PrintEventArgs) Handles PrintDocument1.BeginPrint
        mRow = 0
        newpage = True
        PrintPreviewDialog1.PrintPreviewControl.StartPage = 0
        PrintPreviewDialog1.PrintPreviewControl.Zoom = 1.0
    End Sub
    

    After you preview the mRow variable will indicate that all the rows have been printed. If the user clicks Print or goes back for another Preview, nothing will print. This code also resets the first page to show and the initial Zoom.