Welcome to WindowsClient.net | My Blog | Sign in | Join

Small Business Developer

Sponsors





  • advertise here

Tags

No tags have been created or used yet.

Report Writing On The Cheap - Part 1

For many small business developers purchasing first-class reporting tools is unaffordable or not practical for simple reports.  One alternative is using Excel for reporting, but it requires dealing with COM+ objects and the assumption that the customer has Excel installed.  You could use SpreadsheetML, but it requires knowledge of XML, XSLT, Linq to XML, etc... and still depends on Excel or OpenOffice.  Another option is using the PrintForm component from the Visual Basic Power Packs, but it has limitations (I will do a separate post on using them).  The possibilities go one, but luckily, for those using .Net, Microsoft has the code for you to use.  The System.Drawing.Printing namespace along with GDI+ gives you the tools to make professional looking reports with a little bit of knowledge.  When I was looking for report tool alternatives, I happened across an article by Lee Falin (personal blog http://leefalin.com/blog/) in Visual Studio Magazine (www.visualstudiomagazine.com), April 2005 (locator+ code VS0504GS_T).  In this article, Lee describes how to create a report from a datagrid using GDI+, and I hope everyone reading this blog will take the time to read his article.  With a little understanding you can create reports and not have to worry about 3rd-party software.  Understandable this is not very dynamic, but if you need that type of capability you should be looking at professional software.  What I want to do is expand on his article by going into more depth on the PrintDocument class as well as other functionality.  I am going to break this down into multiple parts with part one focusing on the PrintDocument class and the events you need to handle to print a report.  So let us begin by looking at the PrintDocument class, it's events, and what Microsoft suggests should happen during these events.

PrintDocument Class

The PrintDocument class is going to be the basic building block for our report.  Using the graphics object exposed from the PrintDocument events, we can create the document layout using text, images, and/or graphics just like you would on a Windows Form.  Lee Farlin, in his article, warns that since GDI+ uses unmanaged code, make sure you dispose of any drawing objects you create.  The suggested location is the EndPrint event.  You can add the PrintDocument as a component to your form or create it in the code behind.  In order for the document to be printed, you must handle the PrintDocument events. 

PrintDocument Events 

Below is the sequence of events with snippets to demonstrate how to use them.

1.  PrintDocument.Print method is initiated, starting the chain of events.  I created a simple windows project with one button.  The following code was added to the code behind. 

Private WithEvents myPrintDoc As New PrintDocument 
Private Sub Button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) Handles Button1.Click
    myPrintDoc.Print()
End Sub

2.  As soon as the Print method is executed, the BeginPrint event is raised.  Here you can deal with setting up resources that will be used for the print job.  So you would do things such as initialize fonts, load data, or run validations before the printing process begins.  One example would be to ensure that there are rows in the dataset you are going to use to print.  In that case you could set e.Cancel = true to end the print job if the dataset was empty or had errors.

Private WithEvents myPrintDoc As New PrintDocument 
Private Sub myPrintDoc_BeginPrint(ByVal sender As Object, _
                                  ByVal e As System.Drawing.Printing.PrintEventArgs) _
                                  Handles myPrintDoc.BeginPrint
    If myDataSet.HasErrors Or myDataSet.Tables("MyTable").Rows.Count < 1 Then
        e.Cancel = True
    End If

End Sub

 

3.  Next, before each page is printed, the QueryPageSettings event is raised and you can use the event to adjust the page settings for the page you are about to print.  This means that each page could use different settings and all you would have to do is modify the QueryPageSettingsEventArgs object, using the PageSettings property.  You also have access to a cancel property to cancel the print job.  The below example sets the first page of the report to be printed as landscape.

Private currentPageCount As Integer
Private Sub myPrintDoc_QueryPageSettings(ByVal sender As Object, _
                        ByVal e As System.Drawing.Printing.QueryPageSettingsEventArgs) _
                        Handles myPrintDoc.QueryPageSettings
    If currentPageCount = 1 Then e.PageSettings.Landscape = True
End Sub

4.  After QueryPageSettings, PrintPage is raised.  Here is where the majority of the work is done.  The event exposes a graphics object, which you can use to draw lines, text, bitmaps, etc... on the page. To allow printing over multiple pages you need to set the HasMorePages (default is false) property to true, which will call this event again (QueryPageSettings is also called).  Keep in mind that you will need to set this back to false, otherwise the PrintPage event will keep being raised.  Like the other events you can still cancel the print job by setting e.Cancel = True.  Additional properties of interest are MarginBounds, which gets the rectangular area inside the margins.  The PageBounds property gets the rectangular area for the whole page.  Finally, PageSettings gets the current page's settings.  The image below shows the bounds property rectangles as well as a sample PrintPage event code to write a line of text.  You can see the effect of writing the string at position 0,0, which has no concept of the page bounds, and this will be something you will need to handle in your code.

Private Sub myPrintDoc_PrintPage(ByVal sender As Object, _
                                 ByVal e As System.Drawing.Printing.PrintPageEventArgs) _
                                 Handles myPrintDoc.PrintPage
    e.Graphics.DrawString("Hello World Printing", New Font("Arial", 12, _
              FontStyle.Regular, GraphicsUnit.Pixel), Brushes.Black, 0, 0)
End Sub
image 

5.  Finally, after all pages have been printed, EndPrint is executed.  Like BeginPrint which you can use to initialize fonts, file streams, resources, in EndPrint you would close dispose of those objects.  

Conclusion

 

With this basic understanding of the PrintDocument, we can now move to creating a document layout using GDI+ and the above events.  Hopefully, this gets you interested in what can be achieved using the PrintDocument class.  Part two will focus on how to add text, images, graphics, as well a few tips on handling text (word wrap, multiple pages, print a textfile, etc...).  As always comments are welcome and would be helpfully if you want me to dig deeper into a specific topic.

Comments

No Comments

Leave a Comment

(required) 

(required) 

(optional)

(required)