Home arrow C# arrow Page 5 - Printing Using C#

Printing Using C#

In this article Matthew shows us how to build a print engine in C# allowing us to print our application data easily.

Author Info:
By: Wrox Team
Rating: 4 stars4 stars4 stars4 stars4 stars / 371
January 27, 2003
  1. · Printing Using C#
  2. · Creating a Printing Application
  3. · Creating the Project
  4. · Primitives and Elements
  5. · Pagination and Printing
  6. · Changing the Page Settings
  7. · Conclusion

print this article

Printing Using C# - Pagination and Printing
(Page 5 of 7 )

Now we're at a point where we have an object called Customer that can describe itself on a report by calling methods on an object called PrintElement. Now all we have to do is arrange the elements on the page, handle pagination, deal with headers and footers, provide print preview functionality and actually send the data to the printer. Thanks to the work that's been done with the Framework, this is perhaps the easiest part of the exercise.

More Members

We're going to need to add more members to the PrintEngine class:

  public class PrintEngine : PrintDocument
    // members...
    private ArrayList _printObjects = new ArrayList();
    public Font PrintFont = new Font("Arial", 10);
    public Brush PrintBrush = Brushes.Black;
    public PrintElement Header;
    public PrintElement Footer;
    private ArrayList _printElements;
    private int _printIndex = 0;
    private int _pageNum = 0;

The PrintFont and PrintBrush will be used to define the font and brush that will be used to do the printing. In this case, we're saying that we want to use a ten point Arial font, in black.

The Header and Footer describe elements that contain the header and footer for each page. _printElements describes a list of all the elements, except Header and Footer. _printIndex keeps track of the current position in the _printElements list. Finally, _pageNum keeps track of the current page number.

The first thing to do before we try to print is build up the header and footer elements. We'll create default ones in the constructor:

    public PrintEngine()
      // create the header...
      Header = new PrintElement(null);
      Header.AddText("Page: [pagenum]");

      // create the footer...
      Footer = new PrintElement(null);

The easiest way to develop printing routines is to get the print preview working. This saves a lot of otherwise wasted paper! Luckily for us, the Framework provides a class that containerizes an instance of PrintPreviewControl. Add this method to PrintEngine:

    // ShowPreview - show a print preview...
    public void ShowPreview()
      // now, show the print dialog...
      PrintPreviewDialog dialog = new PrintPreviewDialog();
      dialog.Document = this;

      // show the dialog...

As you can see, PrintPreviewDialog needs to be given a PrintDocument object, so we give it a reference to the PrintEngine object that was used. We then call ShowDialog.

The printing routines have a fairly curious way of working, which I mentioned right at the beginning involved firing events to signal when a page needs to be printed. The first event that gets fired is BeginPrint, but as PrintEngine is inherited from PrintDocument the best way to get at this is to override OnBeginPrint. This is, I'm told, similar to the way that the printing routines in MFC worked.

What we need to do in response to this event is create a new list of PrintElement objects - one for each of the objects we have in our _printObjects list. Add this code to PrintEngine:

    // OnBeginPrint - called when printing starts
    protected override void OnBeginPrint(PrintEventArgs e)
      // reset...
      _printElements = new ArrayList();
      _pageNum = 0;
      _printIndex = 0;

      // go through the objects in the list and create print elements for each one...
      foreach(IPrintable printObject in _printObjects)
        // create an element...
        PrintElement element = new PrintElement(printObject);

        // tell it to print...

As we work through the list we create a new PrintElement object for each IPrintable -supporting object that we have. We then ask each element to print itself through the Print method. At this time, the element will call into IPrintable.Print and the underlying class will tall the various AddText, AddHeader, AddHorizontalRule methods to build up the primitives.

Once we've done that, we can turn our attention to the printing mechanism. This is done through OnPrintPage. Add this code:

    // OnPrintPage - called when printing needs to be done...
    protected override void OnPrintPage(PrintPageEventArgs e)
      // adjust the page number...

The first thing we do is increment the page number. In OnBeginPrint we set this to 0, meaning that on the first page it will be 1. We then want to draw the header element. This will always appear in the top-left hand corner of each page.

The PrintPageEventArgs contains members that tell us everything we need to know about printing. MarginBounds describes a rectangle containing the printable area of the page. Graphics contains a System.Graphics.Drawing object that contains the methods necessary for drawing on the screen or printer. We pass both of these into the header element:

      // now, render the header element...
      float headerHeight = Header.CalculateHeight(this, e.Graphics);
      Header.Draw(this, e.MarginBounds.Top, e.Graphics, e.MarginBounds);
Drawing the footer is a similar deal, except this has to appear at the bottom of the page:
      // also, we need to calculate the footer height...
      float footerHeight = Footer.CalculateHeight(this, e.Graphics);
      Footer.Draw(this, e.MarginBounds.Bottom - footerHeight, e.Graphics, e.MarginBounds);

Once we've done that, we need to calculate the area of the page that's left. This will contain the elements, or rather as many elements as will fit onto the page.

      // now we know the header and footer, we can adjust the page bounds...
      Rectangle pageBounds = new Rectangle(e.MarginBounds.Left,
  (int)(e.MarginBounds.Top + headerHeight), e.MarginBounds.Width,
    (int)(e.MarginBounds.Height - footerHeight - headerHeight));
      float yPos = pageBounds.Top;

Right at the end there we define and set yPos to be the top of the printable area given over to elements. We'll see how this moves down the page as we draw elements.

The _printIndex keeps track of which element has to be drawn next. In OnBeginPage we set this to 0 meaning that the next element we draw will be the first one in _printElements. To loop through the elements, we need to do this:

      // ok, now we need to loop through the elements...
      bool morePages = false;
      int elementsOnPage = 0;
      while(_printIndex < _printElements.Count)
        // get the element...
        PrintElement element = (PrintElement)_printElements[_printIndex];

Once we have the element, we need to find its height. Taking into consideration the current position of yPos, we want to make sure that the element will fit on the page:

        // how tall is the primitive?
        float height = element.CalculateHeight(this, e.Graphics);

        // will it fit on the page?
        if(yPos + height > pageBounds.Bottom)
  // we don't want to do this if we're the first thing on the page...
          if(elementsOnPage != 0)
            morePages = true;

If the element will not fit on the page, we quit the loop - and we'll see why in a moment. One important thing to note here is that if this is the first element on the page (i.e. elementsOnPage = 0), we never want to leave the loop. If it's the first element and it's the first page, it's too big to fit on any page. Rather than breaking the element up, we'll just render it as it is, meaning that the bottom of the element will draw over the footer.

Providing the element will fit, we can tell the element to draw itself, move yPos down to the position of the next element and adjust printIndex so that it points to the next element in the list:

        // now draw the element...
        element.Draw(this, yPos, e.Graphics, pageBounds);

        // move the ypos...
        yPos += height;

        // next...

So what happens if we have more pages? Well, we have to set PrintPageEventArgs.HasMorePages to true. This will result on OnPrintPage being called a second time, in which case we go round the whole thing again:

      // do we have more pages?
      e.HasMorePages = morePages;

Replacing Tokens

When we built our header element we added a line like this:

      Header.AddText("Page: [pagenum]");

The [pagenum] represents a token that we want to replace at print time with a value of some kind, in this case the current page number. When we created the PrintPrimitiveText class, we had this calling into PrintEngine.ReplaceTokens before the text was sent to the printer. Add this method to PrintEngine:

    // ReplaceTokens - take a string and remove any tokens replacing them with values...
    public String ReplaceTokens(String buf)
      // replace...
      buf = buf.Replace("[pagenum]", _pageNum.ToString());

      // return...
      return buf;

Although we've just shown how to replace [pagenum] here, the same will work with other tokens that you define.

Calling "ShowPrintPreview"

To call ShowPrintPreview, we actually need something to print. Add this member to Form1:

  public class Form1 : System.Windows.Forms.Form
    // members...
    PrintEngine _engine = new PrintEngine();

In response to changing the slider value, we want to add Customer objects to _engine. Add this line to Form1 's constructor:

    public Form1()
      // Required for Windows Form Designer support

      // create a default print engine...
Now, add this definition for CreateEngine:
    // CreateEngine - create a print engine and populate it with customers...
    public void CreateEngine(int numCustomers)
      // create a new engine...
      _engine = new PrintEngine();

      // loop through the customers...
      for(int n = 0; n < numCustomers; n++)
        // create the customer...
        Customer theCustomer = new Customer();
        theCustomer.Id = n + 1;
        theCustomer.FirstName = "Darren";
        theCustomer.LastName = "Clarke";
        theCustomer.Company = "Madras inc.";
        theCustomer.Email = "darren@pretendcompany.com";
        theCustomer.Phone = "602 555 1234";

        // add the customer to the list...

There's nothing very complicated about this. All we want to do is create Customer objects and add them to the engine.

Next, add this event handler to make calls to CreateEngine whenever the user changes the slider value:

    private void trackCustomers_Scroll(object sender, System.EventArgs e)

Finally, we can wire up cmdPrintPreview_click:

    private void cmdPrintPreview_Click(object sender, System.EventArgs e)
      // tell the print object to display a preview...

Now we can try running the application and testing the print preview. Leave the slider where it is and click the button. You should see something like this:


Zooming in, you should see this:


Now, close the print preview and change the slider to about a third of the way along. This will create a bundle of Customer objects and you should see more pages on the preview.


That's the basics of printing illustrated. You can actually print the document if you want by clicking the printer button on the toolbar.
blog comments powered by Disqus

- Introduction to Objects and Classes in C#, P...
- Visual C#.NET, Part 1: Introduction to Progr...
- C# - An Introduction
- Hotmail Exposed: Access Hotmail using C#
- Razor Sharp C#
- Introduction to Objects and Classes in C#
- Making Your Code CLS Compliant
- Programming with MySQL and .NET Technologies
- Socket Programming in C# - Part II
- Socket Programming in C# - Part I
- Creational Patterns in C#
- Type Conversions
- Creating Custom Delegates and Events in C#
- Inheritance and Polymorphism
- Understanding Properties in C#

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 

Developer Shed Affiliates


© 2003-2018 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials