Click here to Skip to main content
Click here to Skip to main content
Go to top

Structured Print Document utility

, 14 Jun 2014
Rate this:
Please Sign up or sign in to vote.
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 existsing layout template with the data at run time in order to create te 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 hierarcy 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 queue

TextStyleProvider is the TextStyleProvider 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 properties such including:

Landscape which determines whether or not to print the page in landscape mode,

Sections which is the collection of StructuredprintDocumentArea objects that print on this page.  (These are somewhat analoguous 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 and DataActionsOnNextPage 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 style.
  • BorderColour - The pen colour to draw the border of the section.
  • BorderWidth - The pen width to draw the border of the section.
  • TopBorder, BottomBorder, LeftSideBorder, RightSideBorder - which parts of the border to draw.
  • ForColour - The colour to use for the text.
  • Highlight - The colour to use for the background of the text.
  • TextGutter - The space between the edge of the document area and the text area.
  • VerticalAlignment, 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 "Overriden 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:

[Editor comment: Line breaks used to avoid scrolling.]

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:

[Editor comment: Line breaks used to avoid scrolling.]

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:

[Editor comment: Line breaks used to avoid scrolling.]

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 repreat 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:

Phone list

Employee details

And the same document at run time:

Phone list

Employee details

Future improvements

Currently, the page design is created in code - ideally this should be done using an interactive designer.

History

  • 14 June 2014 - Added more details on what each class does based on received feedback - no code changes.
  • 10 Apr 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Duncan Edwards Jones
Software Developer (Senior)
Ireland Ireland
C# / SQL Server developer
Microsoft MVP 2006, 2007
Visual Basic .NET
Follow on   Twitter   LinkedIn

Comments and Discussions

 
QuestionStyles not working Pinmembermadugula00714-Sep-14 21:06 
AnswerRe: Styles not working PinprofessionalDuncan Edwards Jones14-Sep-14 22:51 
GeneralRe: Styles not working Pinmembermadugula00715-Sep-14 0:03 
GeneralRe: Styles not working PinprofessionalDuncan Edwards Jones15-Sep-14 0:16 
GeneralRe: Styles not working PinprofessionalDuncan Edwards Jones15-Sep-14 0:56 
GeneralRe: Styles not working Pinmembermadugula00715-Sep-14 1:49 
GeneralMy vote of 5 Pinmembermanoj kumar choubey19-Feb-12 19:55 
GeneralCompiling Error Pinmembergdufresne16-Oct-07 19:14 
GeneralRe: Compiling Error PinmemberDuncan Edwards Jones18-Mar-09 11:18 
Generalerror in ColumnFormat.vb Pinmemberscalpa987-Apr-07 23:38 
GeneralWhoa! PinmemberAndrewVos1-Mar-07 7:32 
GeneralRe: Whoa! PinmemberDuncan Edwards Jones13-Mar-07 14:11 
GeneralI was going to give this a 4... PinmemberPaul C Smith22-May-06 13:28 
GeneralRe: I was going to give this a 4... PinmemberDuncan Edwards Jones22-May-06 22:16 
GeneralRuntime Error Pinmemberalbert_khor12-Jan-06 15:11 
GeneralRe: Runtime Error PinmemberDuncan Edwards Jones12-Jan-06 18:05 
GeneralRe: Runtime Error Pinmemberalbert_khor14-Jan-06 21:45 
GeneralRe: Runtime Error PinmemberDuncan Edwards Jones14-Jan-06 23:24 
Generalactually printing PinmemberGeorgeOrwell9-Jan-06 12:46 
GeneralRe: actually printing PinmemberGeorgeOrwell9-Jan-06 12:48 
The problem is actually in the PrintPreview.. if you by-pass the printpreviewdialog and just print the document it works as expected.
GeneralRe: actually printing PinmemberDuncan Edwards Jones9-Jan-06 20:52 
GeneralDesign Mode PinmemberTLWallace.NET2-Jan-06 14:40 
GeneralRe: Design Mode PinmemberDuncan Edwards Jones2-Jan-06 20:48 
GeneralError when compile - require snk file Pinmemberksboy14-Dec-05 19:04 
GeneralRe: Error when compile - require snk file PinmemberDuncan Edwards Jones14-Dec-05 21:24 
GeneralRe: Error when compile - require snk file Pinmemberksboy15-Dec-05 14:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 14 Jun 2014
Article Copyright 2005 by Duncan Edwards Jones
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid