avatar

Andres Jaimes

Printing in C# – The easy way

By Andres Jaimes

Believe me, it is easy. As with any class, before we can use any of the Classes, Events and Objects available to us in the .Net Framework we need to import the Namespaces we need. For this we need 3 namespaces. VB.Net users only required 2 because VB.Net assumes the System Namespace, whereas C# isn’t so kind:

  • System
  • System.Drawing
  • System.Drawing.Printing

These 3 Namespaces contain everything we need for this class, so you will need to add the following lines to the top of your class file:

using System;
using System.Drawing;
using System.Drawing.Printing;

Now we have to inherit the PrintDocument Class. To do this, make the following change to the declaration of your class:

public class PCPrint : System.Drawing.Printing.PrintDocument
{

}

So now we have a shell to work with that inherits from the PrintDocument Class, so lets add some functionality and other code to our class. In our class we will have 2 properties, one to hold the text that we are printing, and one to hold the Font we will be using when printing our document. We will also add a variable to hold the current character we are working with. This is necessary for more than one page texts.

public Font PrinterFont { get; set; }
public string TextToPrint { get; set; }

// Static variable to hold the current character we're currently
// dealing with.
static int curChar;

Adding the Font property allows us to override the default font.

Next we will incorporate some Constructors for our class. Since we are inheriting from a separate class, we need to call the Constructor of the base class. This is done by using base(), this will call the Constructor of our base class, the PrintDocument Class:

public PCPrint() : base()
{
    //Set our Text property to an empty string
    TextToPrint = string.Empty;
}

public PCPrint(string str) : base()
{
    //Set our Text property value
    TextToPrint = str;
}

Now we have our Properties and our Constructors, next we will add a couple methods to our class. In our printing class we will be overriding 2 of the PrintDocument methods, those will be the OnBeginPrint method and the OnPrintPage method. In the OnBeginPrint method we will override the default font with the font we specify, and in the OnPrintPage we will be setting up the specifics of our page. We will be setting the following properties:

  • Page Size
  • Page Orientation (Landscape, Portrait)
  • Top Margin
  • Left Margin

First, the OnBeginPrint method, as with our Constructors we will need to call our base class’s OnBeginPrint method, then we can do our custom work:

/// <summary>
/// Override the default OnBeginPrint method of the PrintDocument Object
/// </summary>
/// <param name=e></param>
/// <remarks></remarks>
protected override void OnBeginPrint(System.Drawing.Printing.PrintEventArgs e)
{
    // Run base code
    base.OnBeginPrint(e);

    //Check to see if the user provided a font
    //if they didn't then we default to Times New Roman
    if (PrinterFont == null)
    {
        //Create the font we need
        PrinterFont = new Font("Times New Roman", 10);
    }
}

As you can see, here we call the base class’s OnBeginPrint method, then we check to see if a font was provided, if no one was, we default to Times New Roman. The OnPrintPage method is quite a bit larger and complex, this is where we will be doing the bulk of our work.

In this method we will be setting the size of the print area (the page size), we will determine if the user selected Landscape or Portrait as the print style, we will determine how many lines we are printing and how many lines will fit in our page size, and finally we will tell the printer whether we have more pages to print. As with our OnBeginPrint method, we will need to call our base class’s OnPrintPage method before doing our customizing of the method. Let’s take a look at our overridden OnPrintPage method:

/// <summary>
/// Override the default OnPrintPage method of the PrintDocument
/// </summary>
/// <param name=e></param>
/// <remarks>This provides the print logic for our document</remarks>
protected override void OnPrintPage(System.Drawing.Printing.PrintPageEventArgs e)
{
    // Run base code
    base.OnPrintPage(e);

    //Declare local variables needed

    int printHeight;
    int printWidth;
    int leftMargin;
    int rightMargin;
    Int32 lines;
    Int32 chars;

    //Set print area size and margins
    {
        printHeight = base.DefaultPageSettings.PaperSize.Height - base.DefaultPageSettings.Margins.Top - base.DefaultPageSettings.Margins.Bottom;
        printWidth = base.DefaultPageSettings.PaperSize.Width - base.DefaultPageSettings.Margins.Left - base.DefaultPageSettings.Margins.Right;
        leftMargin = base.DefaultPageSettings.Margins.Left;  //X
        rightMargin = base.DefaultPageSettings.Margins.Top;  //Y
    }

    //Check if the user selected to print in Landscape mode
    //if they did then we need to swap height/width parameters
    if (base.DefaultPageSettings.Landscape)
    {
        int tmp;
        tmp = printHeight;
        printHeight = printWidth;
        printWidth = tmp;
    }

    //Create a rectangle printing are for our document
    RectangleF printArea = new RectangleF(leftMargin, rightMargin, printWidth, printHeight);

    //Use the StringFormat class for the text layout of our document
    StringFormat format = new StringFormat(StringFormatFlags.LineLimit);

    //Fit as many characters as we can into the print area
    e.Graphics.MeasureString(TextToPrint.Substring(RemoveZeros(ref curChar)), PrinterFont, new SizeF(printWidth, printHeight), format, out chars, out lines);

    //Print the page
    e.Graphics.DrawString(TextToPrint.Substring(RemoveZeros(ref curChar)), PrinterFont, Brushes.Black, printArea, format);

    //Increase current char count
    curChar += chars;

    //Detemine if there is more text to print, if
    //there is the tell the printer there is more coming
    if (curChar < TextToPrint.Length)
    {
        e.HasMorePages = true;
    }
    else
    {
        e.HasMorePages = false;
        curChar = 0;
    }
}

You will notice that in this method we reference a function called RemoveZeros. This is the last function in our class, and it has an important role. We will create a function that will check our value, and if it’s a 0 (not the number, but a memory space with a value of zero – ascii 0 -) we will replace it with a 1 (ascii 1). Zero’s can cause bad things to happen when it comes to printing and determining page size and margins, so to alleviate that we use our RemoveZeros Function:

public int RemoveZeros(ref int value)
{
    //As 0 (ZERO) being sent to DrawString will create unexpected
    //problems when printing we need to search for the first
    //non-zero character in the string.
    while (TextToPrint[value] == '')
    {
        value++;
    }
    return value;
}

And there you have it, our completed printing class. Now I know some are asking “I see this class, but how do I use it?”. Well I’m glad you asked, this class is very easy to use. In your Form’s code add a procedure named printDocument. Then inside that procedure create an instance of the printer class. Set its properties (PrinterFont, TextToPrint) and then issue a print command, like this:

public void PrintDocument()
{
    //Create an instance of our printer class
    PCPrint printer = new PCPrint();
    //Set the font we want to use
    printer.PrinterFont = new Font("Verdana", 10);
    //Set the TextToPrint property
    printer.TextToPrint = Textbox1.Text;
    //Issue print command
    printer.Print();
}

Some other functions and classes you may find useful are DrawRectangle, FillRectangle, Arc, Brushes and Pen. All these basic functions will help you get beautiful reports if you have the patience to put them together.

Based on Printing in C# by PsychoCoder