Click here to Skip to main content
15,878,814 members
Articles / Programming Languages / Visual Basic

Structured Print Document Utility

Rate me:
Please Sign up or sign in to vote.
4.79/5 (19 votes)
14 Jun 2014CPOL6 min read 152.1K   3.5K   95   27
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 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 such properties 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 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 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:

Image 1

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:

Image 2

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:

Image 3

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

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

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

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

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

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

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

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

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

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

Image 4

Phone list

Image 5

Employee details

And the same document at run time:

Image 6

Phone list

Image 7

Employee details

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.

License

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


Written By
Software Developer
Ireland Ireland
C# / SQL Server developer
Microsoft MVP (Azure) 2017
Microsoft MVP (Visual Basic) 2006, 2007

Comments and Discussions

 
PraiseGood one Pin
Bhuvanesh Mohankumar11-Aug-16 7:19
Bhuvanesh Mohankumar11-Aug-16 7:19 
QuestionStyles not working Pin
madugula00714-Sep-14 21:06
madugula00714-Sep-14 21:06 
AnswerRe: Styles not working Pin
Duncan Edwards Jones14-Sep-14 22:51
professionalDuncan Edwards Jones14-Sep-14 22:51 
GeneralRe: Styles not working Pin
madugula00715-Sep-14 0:03
madugula00715-Sep-14 0:03 
GeneralRe: Styles not working Pin
Duncan Edwards Jones15-Sep-14 0:16
professionalDuncan Edwards Jones15-Sep-14 0:16 
GeneralRe: Styles not working Pin
Duncan Edwards Jones15-Sep-14 0:56
professionalDuncan Edwards Jones15-Sep-14 0:56 
GeneralRe: Styles not working Pin
madugula00715-Sep-14 1:49
madugula00715-Sep-14 1:49 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey19-Feb-12 19:55
professionalManoj Kumar Choubey19-Feb-12 19:55 
GeneralCompiling Error Pin
gdufresne16-Oct-07 19:14
gdufresne16-Oct-07 19:14 
GeneralRe: Compiling Error Pin
Duncan Edwards Jones18-Mar-09 11:18
professionalDuncan Edwards Jones18-Mar-09 11:18 
Generalerror in ColumnFormat.vb Pin
scalpa987-Apr-07 23:38
scalpa987-Apr-07 23:38 
GeneralWhoa! Pin
AndrewVos1-Mar-07 7:32
AndrewVos1-Mar-07 7:32 
GeneralRe: Whoa! Pin
Duncan Edwards Jones13-Mar-07 14:11
professionalDuncan Edwards Jones13-Mar-07 14:11 
GeneralI was going to give this a 4... Pin
Paul C Smith22-May-06 13:28
Paul C Smith22-May-06 13:28 
GeneralRe: I was going to give this a 4... Pin
Duncan Edwards Jones22-May-06 22:16
professionalDuncan Edwards Jones22-May-06 22:16 
GeneralRuntime Error Pin
albert_khor12-Jan-06 15:11
albert_khor12-Jan-06 15:11 
GeneralRe: Runtime Error Pin
Duncan Edwards Jones12-Jan-06 18:05
professionalDuncan Edwards Jones12-Jan-06 18:05 
GeneralRe: Runtime Error Pin
albert_khor14-Jan-06 21:45
albert_khor14-Jan-06 21:45 
GeneralRe: Runtime Error Pin
Duncan Edwards Jones14-Jan-06 23:24
professionalDuncan Edwards Jones14-Jan-06 23:24 
Generalactually printing Pin
GeorgeOrwell9-Jan-06 12:46
GeorgeOrwell9-Jan-06 12:46 
GeneralRe: actually printing Pin
GeorgeOrwell9-Jan-06 12:48
GeorgeOrwell9-Jan-06 12:48 
GeneralRe: actually printing Pin
Duncan Edwards Jones9-Jan-06 20:52
professionalDuncan Edwards Jones9-Jan-06 20:52 
GeneralDesign Mode Pin
Terence Wallace2-Jan-06 14:40
Terence Wallace2-Jan-06 14:40 
GeneralRe: Design Mode Pin
Duncan Edwards Jones2-Jan-06 20:48
professionalDuncan Edwards Jones2-Jan-06 20:48 
GeneralError when compile - require snk file Pin
ksboy14-Dec-05 19:04
ksboy14-Dec-05 19:04 

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

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