Structured Print Document Utility






4.79/5 (16 votes)
A set of classes for creating structured documents
Introduction
A structured print document is one which combines a design time template with a run time collection of data items. Most reporting tools (such as Crystal Reports and SSRS) are built using this idea of combining an existing layout template with the data at run time in order to create the desired output.
This implementation prints the most common types of document section - tabular grids, pictures, rich text and labels and has methods that allow the definition of what happens when the amount of data exceeds the space available for it in the template.
In order to separate data and display in the way familiar to user interface developers, there is also the concept of particular styles to control the appearance of sections.
The classes are arranged in a hierarchy whereby a document is made of one or more page templates and a page template is made of one or more document sections.
Classes that Make Up the Structured Print Document
StructuredprintDocumentProvider
This is the main component that does the printing. It has three properties:
DocumentName
is the name of the document as it appears in the print spool queueTextStyleProvider
is theTextStyleProvider
component that provides a set of standard styles to be used in the document.StructuredPrintDocument
is the property that defines the document template that is filled in to create the printed document.
StructuredPrintDocument
This has a number of properties including Mode
which determines whether the document is going to print in design time (layout) or run time mode and Pages
the collection of StructuredprintPage
objects that define the pages in this document.
When a document is in preview mode, it will print a set of grid lines (at whatever grid size is selected) to help with the fine alignment of page sections.
StructuredPrintPage
This represents a single page of the template. This has a number of such properties including:
Landscape
which determines whether or not to print the page in landscape modeSections
which is the collection ofStructuredprintDocumentArea
objects that print on this page (These are somewhat analogous to the individual controls on a windows form.)AdditionalPageTests
which are the checks that are to be performed to decide whether this page template has more pages to print. A single page template may result in printing more than one page if the data to be printed exceeds the space available on the page.DataActionsBefore, DataActionsAfter
andDataActionsOnNextPage
that perform actions on the underlying collections before, after and on each new page of the page template respectively. You can use these to do things like updating a page number variable and so on.
StructuredPrintDocumentArea
This does the work of printing the document. It has properties:
BoundaryRectangle
which is the rectangular section of the page to print in, DataSourceName
which tells the name of the data to be printed
WriterType
that controls which IStructuredPrintDocumentAreaWriterBase
derived class will perform the actual printing, as well as data actions and additional page tests.
Extensibility - The IStructuredPrintDocumentAreaWriterBase interface
Additional Parameters Needed for Specific Document Area Writers
In addition to the properties common to all the document area writer classes provided by the IStructuredPrintDocumentAreaWriterBase
interface, there may be extra parameters needed for a particular document area writer - for example, a picture writer could have a parameter to decide whether to stretch the picture to fit the clipping region and a table writer would need extra parameters to control the width and styles used in the columns.
This is implemented by the SetExtraParameter
method of the interface.
Applying a Style Set to the Document
To provide a common look and feel to the various elements of the printed document, you can create a collection of document paragraph text styles to the document:
The properties of each paragraph text style are:
Font
- The font to use to print text in this styleBorderColour
- The pen colour to draw the border of the sectionBorderWidth
- The pen width to draw the border of the sectionTopBorder, BottomBorder, LeftSideBorder, RightSideBorder
- which parts of the border to drawForColour
- The colour to use for the textHighlight
- The colour to use for the background of the textTextGutter
- The space between the edge of the document area and the text areaVerticalAlignment, HorizontalAlignment
- The alignment of data in the document area
Design Mode and Runtime Mode
In the design mode, the document prints each page only once and for each document section, it prints the data element name rather than the actual data used:
In the run time mode, each page template is populated with the data and printed as long as there are more pages needed. This means that the data actions and additional page tests are executed:
Getting Data into the Document
There are two ways of getting data into the document. The first chance is before the document is printed using the LoadDataItem
method, e.g.:
Me.StructuredPrintDocumentProvider1.StructuredPrintDocument.
LoadDataItem("Employee", "", _Employees)
Alternatively, if the document is printing and it comes to a data element for which there is no data held in the document, it will raise a DocumentContentQuery
event to ask for the data:
Private Sub StructuredPrintDocumentProvider1_DocumentContentQuery(_
ByVal sender As Object, ByVal e As System.EventArgs)
Handles StructuredPrintDocumentProvider1.DocumentContentQuery
With CType(e, DocumentContentQueryEventArgs)
If .PropertyName = "TodaysDate" Then
.Scope =
DocumentContentQueryEventArgs.DocumentDataScope.GlobalScope
.Value = Now.ToLongDateString
End If
End With
End Sub
Invariant Sections: Labels and Images
There are also fixed elements within a structured document such as labels and pictures which have a constant content at design time. These are commonly referred to as labels and pictures.
Previewing the Structured Document
To preview the document, you need to have a System.Windows.Forms.PrintPreviewDialog
control on your application form and set its Document
member to the StructuredPrintDocument.PrintDocument
property. This will then take over the printing according to the pages and sections you have added to it:
Private Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Me.PrintPreviewDialog1.Document = _
Me.StructuredPrintDocumentProvider1.
StructuredPrintDocument.PrintDocument
Me.PrintPreviewDialog1.ShowDialog()
End Sub
You can preview the document in design mode or runtime mode by setting the StructuredPrintDocument.Mode
member:
If Me.chkDesign.Checked Then
Me.StructuredPrintDocumentProvider1.StructuredPrintDocument.Mode = _
StructuredPrintDocument.PrintMode.DesignTime
Else
Me.StructuredPrintDocumentProvider1.StructuredPrintDocument.Mode = _
StructuredPrintDocument.PrintMode.RunTime
End If
In the design mode, each page of the document is shown once and the data element name is shown rather than the actual data.
Worked Example - An Employee List
The employee
list comes from a collection class that inherits from ArrayList
:
Public Class Employee
#Region "Private members"
Private _Name As String
Private _Extension As String
Private _StartDate As Date
#End Region
#Region "Public interface"
Public ReadOnly Property Name() As String
Get
Return _Name
End Get
End Property
Public ReadOnly Property Extension() As String
Get
Return _Extension
End Get
End Property
Public ReadOnly Property StartDate() As Date
Get
Return _StartDate
End Get
End Property
Public Photo As System.Drawing.Bitmap
#End Region
#Region "Public constructor"
Public Sub New(ByVal NameIn As String, _
ByVal ExtensionIn As String, ByVal DateStartedIn As Date)
_Name = NameIn
_Extension = ExtensionIn
_StartDate = DateStartedIn
End Sub
#End Region
End Class
Public Class Employees
Inherits ArrayList
#Region "Overridden interface"
Public Shadows Function Add(_
ByVal Value As Employee) As Integer
Return MyBase.Add(Value)
End Function
Public Shadows Property Item(_
ByVal Index As Integer) As Employee
Get
Return MyBase.Item(Index)
End Get
Set(ByVal Value As Employee)
MyBase.Item(Index) = Value
End Set
End Property
#End Region
End Class
And the requirement is to print a phone list with the employee
name and extension and then for each employee
, to print a summary sheet with all the properties. The phone list is to be printed as a table in landscape format and the employee
detail sheet is to be printed in portrait mode. Therefore, we start by defining the two pages:
With StructuredPrintDocumentProvider1
.StructuredPrintDocument = New StructuredPrintDocument
.StructuredPrintDocument.DocumentName = "Employee Listing"
'Add two new pages...
.StructuredPrintDocument.AddPage(_
New StructuredPrintPage("Employee List"))
.StructuredPrintDocument.AddPage(_
New StructuredPrintPage("Employee Details"))
End With
Then, we design the first page which has a heading label, a fixed picture, a dynamic label that gets the date and a grid which holds the employee
phone list:
With StructuredPrintDocumentProvider1.StructuredPrintDocument.
Pages.Page("Employee List")
.Landscape = True
.AddPicture("Heading", Me.PictureBox1.Image, "HeadingPicture", _
New System.Drawing.Rectangle(650, 300, 150, 300), False)
.AddLabel("Heading", "Employee Phone List", "Subheading", _
New System.Drawing.Rectangle(150, 255, 800, 30))
.AddSection(New StructuredPrintDocumentArea("Employee List"))
With .Section("Employee List")
.WriterType = GetType(StructuredPrintDocumentAreaTableWriter)
.BoundaryRectangle = _
New System.Drawing.Rectangle(150, 300, 450, 300)
.DataSourceName = "Employee"
'Print another "Employee List" page if it
'overflows the first page
.AdditionalPageTests.Add(New AdditionalPageTest("Employee", _
AdditionalPageTest.AdditionalPageTestTypes.NotAtEndOfCollection))
End With
'Set up the column formats for the table
Dim EmployeeTelNoTableFormat As ColumnFormatCollection = _
ColumnFormatCollection.CreateDefaultColumnFormatCollection(_
GetType(Employee), New System.Drawing.Font(_
System.Drawing.FontFamily.GenericSerif, 12))
EmployeeTelNoTableFormat.Column("Name").Width = 300
EmployeeTelNoTableFormat.Column("Extension").Width = 150
EmployeeTelNoTableFormat.Column("StartDate").PrintColumn = False
'Make the header italic for extension...
EmployeeTelNoTableFormat.Column("Extension").HeadingTextStyle.Font = _
New System.Drawing.Font(EmployeeTelNoTableFormat.Column("Extension").
HeadingTextStyle.Font, Drawing.FontStyle.Italic Or _
Drawing.FontStyle.Bold)
.Section("Employee List").SetExtraParameter("ColumnFormats", _
EmployeeTelNoTableFormat)
.AddSection(New StructuredPrintDocumentArea("Todays Date"))
With .Section("Todays Date")
.WriterType = GetType(StructuredPrintDocumentAreaTextWriter)
.BoundaryRectangle = _
New System.Drawing.Rectangle(150, 600, 450, 30)
.DataSourceName = "TodaysDate"
.StyleName = "Body"
End With
End With
After the employee
phone list is finished, we want to go back to the employee
collection to start the next set of pages:
StructuredPrintDocumentProvider1.StructuredPrintDocument.
Pages.Page(1).DataActionsBefore.Add(New DocumentDataAction("Employee", _
DocumentDataAction.DataSetMoveActions.MoveToFirstRecord))
Now we have another page description for each employee in the list:
With StructuredPrintDocumentProvider1.StructuredPrintDocument.Pages
.Page(1).AddPicture("Heading", Me.PictureBox1.Image, _
"HeadingPicture", _
New System.Drawing.Rectangle(150, 150, 90, 150), False)
.Page(1).Sections.Item(0).SetExtraParameter("Stretch", True)
.Page(1).AddLabel("EmployeeName", "Employee Name", "Body", _
New System.Drawing.Rectangle(150, 350, 250, 30))
.Page(1).AddSection(New StructuredPrintDocumentArea(_
"Employee Name", "Employee.Name", GetType(StructuredDocuments.
StructuredDocuments.StructuredPrintDocumentAreaTextWriter), _
New System.Drawing.Rectangle(300, 350, 250, 30)))
.Page(1).Section("Employee Name").StyleName = "Body"
.Page(1).AddLabel("EmployeeExtension", "Extension", "Body", _
New System.Drawing.Rectangle(150, 450, 250, 30))
.Page(1).AddSection(New StructuredPrintDocumentArea(_
"Employee Extension", "Employee.Extension", _
GetType(StructuredDocuments.StructuredDocuments.
StructuredPrintDocumentAreaTextWriter), _
New System.Drawing.Rectangle(300, 450, 250, 30)))
.Page(1).Section("Employee Extension").StyleName = "Body"
.Page(1).AddLabel("EmployeeStartDate", "Joined", "Body", _
New System.Drawing.Rectangle(150, 550, 250, 30))
.Page(1).AddSection(New StructuredPrintDocumentArea(_
"Employee Start Date", "Employee.StartDate",
GetType(StructuredDocuments.StructuredDocuments.
StructuredPrintDocumentAreaTextWriter), _
New System.Drawing.Rectangle(300, 550, 250, 30)))
.Page(1).Section("Employee Start Date").StyleName = "Body"
'Make Page 2 repeat for each employee...
.Page("Employee Details").DataActionsOnNextPage.
Add(New DocumentDataAction("Employee",
DocumentDataAction.DataSetMoveActions.MoveToNextRecord))
.Page("Employee Details").AdditionalPageTests.
Add(New AdditionalPageTest("Employee",
AdditionalPageTest.AdditionalPageTestTypes.NotAtEndOfCollection))
End With
This results in the following document in design mode:
And the same document at run time:
Future Improvements
Currently, the page design is created in code - ideally, this should be done using an interactive designer.
History
- 14th June, 2014 - Added more details on what each class does based on received feedback - no code changes
- 10th April, 2006 - Updated the code by adding "
TypeConverter
" and "Designer
" classes to serialise the print document template to the designer part of the Windows Forms code.