Here is the class I used to use for printouts. Search for ">>" to find the
places you need to customise it.
Nowadays I don't bother using the Printing objects in VB. I output Word or
Excel documents instead, it is a much better approach - allows the user to
save, modify or email the resulting report.
-SurturZ
Imports System.ComponentModel
''' <summary>
''' Report Design Pattern.
''' To use this report, simply create an object then execute the .Print() or
..PrintPreview() methods.
''' Use .Dispose to clean up object when finished
''' </summary>
''' <remarks></remarks>
Public Class Report
Implements IDisposable
#Region "Report Parameters"
'This section has code for the input parameters of the report
Private mstrReportName As String
Public ReadOnly Property ReportName() As String
Get
Return mstrReportName
End Get
End Property
Private mdteStartDate As Date
''' <summary>
''' First Date to include in the report
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property ReportStartDate() As Date
Get
Return mdteStartDate
End Get
End Property
Private mdteFinishDate As Date
''' <summary>
''' Last date to include in the report
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property ReportFinishDate() As Date
Get
Return mdteFinishDate
End Get
End Property
#End Region
#Region "Report Contents"
'This section has code for the output contents of the report - usually
an array or dataset of some sort
Private mstrContent(,) As String
#End Region
#Region "Constructors / Destructors"
''' <summary>
''' Creates the report object
''' </summary>
''' <param name="StartDate">First date to include in the report</param>
''' <param name="FinishDate">Last date to include in the report</param>
''' <remarks></remarks>
Public Sub New(ByVal ReportName As String, ByVal StartDate As Date,
ByVal FinishDate As Date)
'>>TODO: Customise this method
'Initialise variables
Call New_SetFonts()
'store input parameters
mdteStartDate = StartDate
mdteFinishDate = FinishDate
mstrReportName = ReportName
'generate the report
Dim intRows As Integer = CInt(DateDiff(DateInterval.Day,
mdteStartDate, mdteFinishDate)) + 1
Dim intCols As Integer = 5
ReDim mstrContent(intRows - 1, intCols - 1) '0-based
For r As Integer = 0 To intRows - 1
mstrContent(r, 0) = Format(DateAdd(DateInterval.Day, r,
mdteStartDate), "Short Date")
mstrContent(r, 1) = "Col 1"
mstrContent(r, 2) = "Col 2"
mstrContent(r, 3) = "Col 3"
mstrContent(r, 4) = "Col 4"
Next r
End Sub
''' <summary>
''' Define fonts. This method only called from Sub New()
''' </summary>
''' <remarks></remarks>
Private Sub New_SetFonts()
'>>TODO: Customise this method
'Define fonts - remember to update .Dispose routine as well.
mfntPageHeader = New
Font(System.Drawing.FontFamily.GenericSansSerif, 8, FontStyle.Regular,
GraphicsUnit.Point)
mfntPageFooter = New
Font(System.Drawing.FontFamily.GenericSansSerif, 8, FontStyle.Regular,
GraphicsUnit.Point)
mfntReportHeader = New Font(System.Drawing.FontFamily.GenericSerif,
16, FontStyle.Regular, GraphicsUnit.Point)
mfntReportFooter = New Font(System.Drawing.FontFamily.GenericSerif,
16, FontStyle.Regular, GraphicsUnit.Point)
mfntContentHeading = New
Font(System.Drawing.FontFamily.GenericMonospace, 10, FontStyle.Bold,
GraphicsUnit.Point)
mfntContentBody = New
Font(System.Drawing.FontFamily.GenericMonospace, 10, FontStyle.Regular,
GraphicsUnit.Point)
End Sub
Private disposed As Boolean = False ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposed Then
If disposing Then
' TODO: free managed resources when explicitly called
'>>TODO: If any new fonts defined in New_SetFonts, add a
..Dispose call here for them
mfntPageHeader.Dispose()
mfntPageFooter.Dispose()
mfntReportHeader.Dispose()
mfntReportFooter.Dispose()
mfntContentHeading.Dispose()
mfntContentBody.Dispose()
End If
' TODO: free shared unmanaged resources
End If
Me.disposed = True
End Sub
#Region " IDisposable Support "
' This code added by Visual Basic to correctly implement the disposable
pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal
disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overrides Sub Finalize()
' Do not change this code. Put cleanup code in Dispose(ByVal
disposing As Boolean) above.
Dispose(False)
MyBase.Finalize()
End Sub
#End Region
#End Region
#Region "Printing"
'If you add any fonts here, update .New_SetFonts() and .Dispose()
Private mfntPageHeader As Font
Private mfntPageFooter As Font
Private mfntReportHeader As Font
Private mfntReportFooter As Font
Private mfntContentHeading As Font
Private mfntContentBody As Font
'Following variables used by .PrintPage()
Private mintLinesOnSinglePage As Integer
Private mintLinesOnFirstPage As Integer
Private mintLinesOnLastPage As Integer
Private mintLinesOnMiddlePages As Integer
Private mintTotalPageCount As Integer 'Total number of pages when printed
Private mintCurrentPage As Integer 'current page number being printed.
0-based (add 1 for human readable version)
Private mintContentColWidth() As Integer
Private mstrContentHeading As String
''' <summary>
''' Create PrintDocument object
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Private Function MakeDocument() As Printing.PrintDocument
'reset pagination counter
mintCurrentPage = 0
'create the document object
Dim pdcNew As New Printing.PrintDocument
pdcNew.DocumentName = mstrReportName
'wire up event handlers to handle pagination
AddHandler pdcNew.PrintPage, AddressOf PrintPage
AddHandler pdcNew.QueryPageSettings, AddressOf QueryPageSettings
Return pdcNew
End Function
''' <summary>
''' Prints the current page
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub PrintPage(ByVal sender As System.Object, ByVal e As
System.Drawing.Printing.PrintPageEventArgs)
'>>TODO: Customise this method
'
'In this class, a report consists of the following:
'
'PageHeader - printed at the top of each page
'ReportHeader - printed on first page of report, under the page header
'ContentHeading - heading for content (usually heading row of a table)
'Content - printed on each page in between any headers and footers
on that page, usually a table
'ReportFooter - printed on the last page of the report, above the
Page Footer
'PageFooter - printed at the bottom of each page
If mintCurrentPage = 0 Then
Call PrintPage_Initialise(e) ' work out pagination etc at start
of printing
End If
'work out which rows in mstrContent() are being printed on this page
Dim intStartElement As Integer
Dim intFinishElement As Integer
If mintTotalPageCount = 1 Then
'single page report
intStartElement = mstrContent.GetLowerBound(0)
intFinishElement = mstrContent.GetUpperBound(0)
ElseIf mintCurrentPage = 0 Then
'first page of multipage report
intStartElement = mstrContent.GetLowerBound(0)
intFinishElement = mintLinesOnFirstPage - 1
ElseIf mintCurrentPage = mintTotalPageCount - 1 Then
'last page of multipage report
intStartElement = mintLinesOnFirstPage + (mintTotalPageCount -
2) * mintLinesOnMiddlePages
intFinishElement = mstrContent.GetUpperBound(0)
Else
'one of the middle pages
intStartElement = mintLinesOnFirstPage + (mintCurrentPage - 1) *
mintLinesOnMiddlePages
intFinishElement = mintLinesOnFirstPage - 1 + mintCurrentPage *
mintLinesOnMiddlePages
End If
'Now do the actual printing
Dim g As Graphics = e.Graphics 'shortcut
Dim x As Single = e.MarginBounds.Left '"Cursor" location
Dim y As Single = e.MarginBounds.Top '"Cursor" location
'g.DrawRectangle(Pens.Black, e.MarginBounds) '>>DEBUG: use this line
to check margins
'Print Page Header
g.DrawString("Page Header", mfntPageHeader, Brushes.Black, x, y)
y += mfntPageHeader.GetHeight(g)
'Print Report Header on first page
If mintCurrentPage = 0 Then
g.DrawString("Report Header", mfntReportHeader, Brushes.Black,
x, y)
y += mfntReportHeader.GetHeight(g)
End If
'Print Content Heading
g.DrawString(mstrContentHeading, mfntContentHeading, Brushes.Black,
x, y)
y += mfntContentHeading.GetHeight(g)
'Print Content
Dim strContentBody As String = ""
Dim col As Integer = 0
For row As Integer = intStartElement To intFinishElement
strContentBody = ""
col = 0 : strContentBody &= Left(mstrContent(row, col) &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 1 : strContentBody &= Left(mstrContent(row, col) &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 2 : strContentBody &= Left(mstrContent(row, col) &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 3 : strContentBody &= Left(mstrContent(row, col) &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 4 : strContentBody &= Left(mstrContent(row, col) &
Space(mintContentColWidth(col)), mintContentColWidth(col))
g.DrawString(strContentBody, mfntContentBody, Brushes.Black, x, y)
y += mfntContentBody.GetHeight(g)
Next row
'Print Report Footer on last page
If mintCurrentPage = mintTotalPageCount - 1 Then
g.DrawString("*** End of Report ***", mfntReportFooter,
Brushes.Black, x, y)
y += mfntReportFooter.GetHeight(g)
End If
'Print Page Footer at bottom of page
Dim strPageCount As String = "Page " & (mintCurrentPage +
1).ToString & " of " & mintTotalPageCount.ToString
Dim szfFooter As SizeF = g.MeasureString(strPageCount, mfntPageFooter)
Dim sngFooterX As Single = e.MarginBounds.Right - szfFooter.Width
'right align
Dim sngFooterY As Single = e.MarginBounds.Bottom - szfFooter.Height
'bottom align
g.DrawString(mstrReportName, mfntPageFooter, Brushes.Black, x,
sngFooterY)
g.DrawString(strPageCount, mfntPageFooter, Brushes.Black,
sngFooterX, sngFooterY)
y += mfntReportFooter.GetHeight(g)
' If more lines exist, print another page.
If mintCurrentPage = mintTotalPageCount - 1 Then
'last page
e.HasMorePages = False
mintCurrentPage = 0 ' reset for print through print preview form
Else
e.HasMorePages = True
mintCurrentPage += 1
End If
'>>TODO: test!
End Sub
Private Sub PrintPage_Initialise(ByVal e As
System.Drawing.Printing.PrintPageEventArgs)
'Pagination Calculations
'>>TODO: This sub is slow - optimise
Dim g As Graphics = e.Graphics 'shortcut
Dim sngPageHeaderSize As Single = mfntPageHeader.GetHeight(g)
'Change if more than one line
Dim sngPageFooterSize As Single = mfntPageFooter.GetHeight(g)
'Change if more than one line
Dim sngReportHeaderSize As Single = mfntReportHeader.GetHeight(g)
'Change if more than one line
Dim sngReportFooterSize As Single = mfntReportFooter.GetHeight(g)
'Change if more than one line
Dim sngContentHeadingSize As Single =
mfntContentHeading.GetHeight(g) 'Change if more than one line
Dim recTemp As Rectangle
recTemp = GetMarginBounds(GetPageSettings(Page_ENUM.First,
e.PageSettings.PrinterSettings))
mintLinesOnSinglePage = CInt((recTemp.Height - sngPageHeaderSize -
sngReportHeaderSize - sngReportFooterSize - sngPageFooterSize -
sngContentHeadingSize) / mfntContentBody.GetHeight(g)) 'this is only used if
the whole report fits on a single page
mintLinesOnFirstPage = CInt((recTemp.Height - sngPageHeaderSize -
sngReportHeaderSize - sngPageFooterSize - sngContentHeadingSize) /
mfntContentBody.GetHeight(g))
recTemp = GetMarginBounds(GetPageSettings(Page_ENUM.Last,
e.PageSettings.PrinterSettings))
mintLinesOnLastPage = CInt((recTemp.Height - sngPageHeaderSize -
sngReportFooterSize - sngPageFooterSize - sngContentHeadingSize) /
mfntContentBody.GetHeight(g))
recTemp = GetMarginBounds(GetPageSettings(Page_ENUM.Middle,
e.PageSettings.PrinterSettings))
mintLinesOnMiddlePages = CInt((recTemp.Height - sngPageHeaderSize -
sngPageFooterSize - sngContentHeadingSize) / mfntContentBody.GetHeight(g))
'Work out total number of pages
If mstrContent.GetUpperBound(0) + 1 <= mintLinesOnSinglePage Then
'we add one for the content heading
mintTotalPageCount = 1
Else
Dim intTemp As Integer = mstrContent.GetUpperBound(0) + 1 'total
line count
mintTotalPageCount = 2 : intTemp = intTemp -
mintLinesOnFirstPage - mintLinesOnLastPage
If intTemp > 0 Then
mintTotalPageCount += intTemp \ (mintLinesOnMiddlePages)
If intTemp Mod mintLinesOnMiddlePages > 0 Then
mintTotalPageCount += 1
End If
End If
'Define column widths for content
ReDim mintContentColWidth(4) '>>TODO: change to correct number of
columns in report
mintContentColWidth(0) = 11 'include any inter-column space to the
right of column
mintContentColWidth(1) = 6 'include any inter-column space to the
right of column
mintContentColWidth(2) = 6 'include any inter-column space to the
right of column
mintContentColWidth(3) = 6 'include any inter-column space to the
right of column
mintContentColWidth(4) = 6 'include any inter-column space to the
right of column
'Work out content heading '>>TODO: Customise
Dim col As Integer = 0
mstrContentHeading = ""
col = 0 : mstrContentHeading &= Left("Date" &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 1 : mstrContentHeading &= Left("Head1" &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 2 : mstrContentHeading &= Left("Head2" &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 3 : mstrContentHeading &= Left("Head3" &
Space(mintContentColWidth(col)), mintContentColWidth(col))
col = 4 : mstrContentHeading &= Left("Head4" &
Space(mintContentColWidth(col)), mintContentColWidth(col))
End Sub
''' <summary>
''' Determines page settings for current page e.g. Orientation
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub QueryPageSettings(ByVal sender As System.Object, ByVal e As
System.Drawing.Printing.QueryPageSettingsEventArgs)
e.PageSettings = GetPageSettings(mintCurrentPage)
End Sub
Private Enum Page_ENUM As Integer
First
Middle
Last
End Enum
Private Overloads Function GetPageSettings(ByVal PageNum As Integer) As
System.Drawing.Printing.PageSettings
Dim enuPage As Page_ENUM
If PageNum = 0 Then
enuPage = Page_ENUM.First
ElseIf PageNum = mintTotalPageCount Then
enuPage = Page_ENUM.Last
Else
enuPage = Page_ENUM.Middle
End If
Return GetPageSettings(enuPage)
End Function
Private Overloads Function GetPageSettings(ByVal PageNum As Page_ENUM,
Optional ByVal InPrinterSettings As System.Drawing.Printing.PrinterSettings =
Nothing) As System.Drawing.Printing.PageSettings
'>>TODO: Customise this method
Dim pgsTemp As System.Drawing.Printing.PageSettings
If IsNothing(InPrinterSettings) Then
pgsTemp = New System.Drawing.Printing.PageSettings()
Else
pgsTemp = New
System.Drawing.Printing.PageSettings(InPrinterSettings)
End If
Select Case PageNum
Case Page_ENUM.First
pgsTemp.Landscape = True
Case Else
pgsTemp.Landscape = False
End Select
Return pgsTemp
End Function
''' <summary>
''' Returns the margin bounds for a particular PageSettings - used to
work out pagination for a report that mixes portrait and landscape pages
''' </summary>
''' <param name="InPageSettings"></param>
''' <returns></returns>
''' <remarks></remarks>
Private Function GetMarginBounds(ByVal InPageSettings As
System.Drawing.Printing.PageSettings) As Rectangle
Dim recTemp As New Rectangle
With InPageSettings
recTemp.X = .Bounds.X + .Margins.Left
recTemp.Y = .Bounds.Y + .Margins.Top
recTemp.Width = .Bounds.Width - .Margins.Left - .Margins.Right
recTemp.Height = .Bounds.Height - .Margins.Top - .Margins.Bottom
End With
Return recTemp
End Function
''' <summary>
''' Print the report
''' </summary>
''' <remarks></remarks>
Public Sub Print()
'Normally no need to customise this method
Dim docOutput As Printing.PrintDocument = MakeDocument()
docOutput.Print()
docOutput.Dispose()
End Sub
''' <summary>
''' Preview the Report on screen
''' </summary>
''' <remarks></remarks>
Public Sub PrintPreview(Optional ByVal Owner As Form = Nothing)
'Normally no need to customise this method
Dim ppvPreview As New PrintPreviewDialog
ppvPreview.Document = MakeDocument()
ppvPreview.FindForm.WindowState = FormWindowState.Maximized
If IsNothing(Owner) Then
ppvPreview.ShowDialog()
Else
ppvPreview.ShowDialog(Owner)
End If
ppvPreview.Dispose()
End Sub
#End Region
End Class