Click here to Skip to main content
13,349,539 members (49,148 online)
Click here to Skip to main content
Add your own
alternative version


82 bookmarked
Posted 1 Mar 2004

Print Windows Forms w/o using API

, 27 Nov 2008
Rate this:
Please Sign up or sign in to vote.
Print Windows Forms using 100% managed code



This article was written well before Visual Studio 2008. Visual Studio 2008 has a PrintForm component making this project moot. The component works very well. Visual Studio 2005 may have something as well (like a PrintForm class), but I have not checked this out. Look here for further information.


This article describes how to send the image of a Form to a printer -- a screenshot, more or less, but using no API or other unmanaged code. This code will be able to print Forms using 2 lines of code. Generally, it redraws the Form and its controls on the printer's Graphics object.

This code is fairly limited but can print most Forms containing standard controls. Within the PrintForm class, a method is required to print each type of control. Included in this class are methods to print the following controls:

  • GroupBox
  • CheckBox
  • TextBox
  • RadioButton
  • Label
  • Button

Adding support for other controls is fairly easy. Add a method and code to draw the control. Add a couple of lines in the DrawControl method to call your new method.


This solution came out of a work requirement. I needed to be able to print an entire Form. I saw many solutions that use the API to grab a screenshot, but I had to use managed code to perform the same task. Nothing I found helped.

Using the Code

To print a Form follow these steps:

  1. Add the PrintForm class into a new or existing Windows Forms project, or use the provided project.
  2. Instantiate a PrintForm object.
  3. Call one of the Print methods.

In the included test app, a button kicks off the printing process:

    this.Cursor = Cursors.WaitCursor;

    PrintForm pf = new PrintForm(this);
catch (Exception ex)
    MessageBox.Show(this, ex.Message);
    this.Cursor = Cursors.Default;

The two lines of code in the try block are all that is required to print a Form.

All the work is contained in the single class PrintForm. I've provided two constructors:

public PrintForm()

public PrintForm(Form frm)
    _frm = frm;

The 2nd constructor sets the Class-level variable to the Form that you want to print.

There are 2 class-level variables that are needed. They are declared as:

private Form _frm = null;
private int _iCurrentPageIndex = 0;
  • _frm stores a reference to the Form to be printed.
  • _iCurrentPageIndex keeps track of the page which is being printed. Pagination is a little tricky due to the way the Framework is setup. This will be covered later.

There are two methods for printing the Form.

  • Print() - uses the Form that should have been set in the constructor.
  • Print(Form frm) - specifies the Form to print.

The latter contains the code which sets up and initiates the printing process. This is the method which will be examined.

public void Print(Form frm)
    if (frm == null)

    // Setup a PrintDocument

    PrintDocument pd = new PrintDocument();
    pd.BeginPrint += new PrintEventHandler(this.PrintDocument_BeginPrint);
    pd.PrintPage += new PrintPageEventHandler(this.PrintDocument_PrintPage);

    // Setup & show the PrintPreviewDialog

    PrintPreviewDialog ppd = new PrintPreviewDialog();
    ppd.Document = pd;

The first block of code creates a PrintDocument object that is used to do the work. The two main PrintDocument events get hooked to matching PrintForm methods described below.

The second block of code creates a PrintPreviewDialog object used to display exactly what will be printed. This will save you lots of paper during testing!

private void PrintDocument_BeginPrint(object sender, PrintEventArgs e)
    _iCurrentPageIndex = 0;

This is the first of two methods that get hooked up in the Print method. All it does is reset the current page index. It also could have been done in the Print method, but this seems a more appropriate place.

private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
    // Set the page margins

    Rectangle rPageMargins = new Rectangle(e.MarginBounds.Location, 

    // Generate the offset origins for the printing window

    Point[] ptOffsets = GeneratePrintingOffsets(rPageMargins);

    // Make sure nothing gets printed in the margins


    // Draw the rest of the Form using the calculated offsets

    Point ptOffset = new Point(-ptOffsets[_iCurrentPageIndex].X, 
    ptOffset.Offset(rPageMargins.X, rPageMargins.Y);
    DrawForm(e.Graphics, ptOffset);

    // Determine if there are more pages

    e.HasMorePages = (_iCurrentPageIndex < ptOffsets.Length - 1);


This is the second of two methods that get hooked up in the Print method. This method is called before each page is printed. It provides a PrintPageEventArgs object which contains the Graphics object on which the Form will be drawn. This is where pagination gets tricky. The page and its margins do not move. Therefore, we have to offset our drawing each time this method gets called.

First, we setup a Rectangle object to the print margins we want. The PrintPageEventArgs contains several properties that we can use. For this project I chose the MarginBounds property which contains the page margins.

Next, pagination is calculated using the GeneratePrintingOffets method which is described below. The data this method generates is the same every time for a given Form, so it is rather inefficient to make this calculation each time this gets called. The reason it cannot be pre-calculated is the page margins aren't known outside this method (they come from the PrintPageEventArgs object). If anyone knows of a different way to determine the page margins, please let me know and I'll update this. In such a case, the functionality could be moved to the PrintDocument_BeginPrint method and the offsets stored in a class-level Point array.

Next, Graphics.SetClip sets the bounds of the drawing area. Anything drawn outside this rectangle will not be displayed.

Then, draw the entire Form using the calculated offsets. Effectively, this divides the Form into rectangles the size of the printing area. This method is described below.

Next, the PrintPageEventArgs must be told if there are more pages to be printed. Each offset that is calculated is a page that will be printed, so it's easy to determine if there are more pages.

Finally, don't forget to increment the page index.

private Point[] GeneratePrintingOffsets(Rectangle rMargins)
    // Setup the array of Points

    int x = (int)Math.Ceiling((double)(_frm.Width) / 
    int y = (int)Math.Ceiling((double)(_frm.Height) / 
    Point[] arrPoint = new Point[x * y];

    // Fill the array

    for (int i = 0; i < y; i++)
        for (int j = 0; j < x; j++)
            arrPoint[i * x + j] = new Point(j * rMargins.Width, 
              i * rMargins.Height);

    return arrPoint;

This method calculates the offsets for each page based on the page margins. It essentially divides the Form into rectangles of the specified size. The array of points stores the upper-left corner of each rectangle. To print each page, the Form is drawn so that this offset is located at the origin of the page margins.

private void DrawForm(Graphics g, Point ptOffset)
    // Calculate the Title Bar rectangle

    int iBarHeight = (int)g.MeasureString(_frm.Text, _frm.Font).Height;
    Rectangle rTitleBar = new Rectangle(ptOffset.X, ptOffset.Y,
         _frm.Width, iBarHeight + 2);

    // Draw the rest of the Form under the Title Bar

    ptOffset.Offset(0, rTitleBar.Height);
    g.FillRectangle(new SolidBrush(_frm.BackColor), ptOffset.X, 
        ptOffset.Y, _frm.Width, _frm.Height);

    // Draw the rest of the controls

    DrawControl(_frm, ptOffset, g);

    // Draw the Form's Title Bar

    Bitmap bmp = Bitmap.FromHicon(_frm.Icon.Handle);
    g.FillRectangle(new SolidBrush(SystemColors.ActiveCaption), rTitleBar);
        new SolidBrush(SystemColors.ActiveCaptionText),
        rTitleBar.X + rTitleBar.Height, // adding the width of the icon

        rTitleBar.Y + (rTitleBar.Height / 2) - 
          (g.MeasureString(_frm.Text, _frm.Font).Height) / 2);

    // Draw the Title Bar buttons

    Size s = new Size(16, 14); // size determined from graphics program

        ptOffset.X + _frm.Width - s.Width,
        ptOffset.Y + (rTitleBar.Height / 2) - 
            (s.Height / 2) - rTitleBar.Height,
        ptOffset.X + _frm.Width - (s.Width * 2) - 1,
        ptOffset.Y + (rTitleBar.Height / 2) - (s.Height / 2)
        - rTitleBar.Height,
        (_frm.WindowState == FormWindowState.Maximized ? 
            CaptionButton.Restore : CaptionButton.Maximize),
        ptOffset.X + _frm.Width - (s.Width * 3 - 1),
        ptOffset.Y + (rTitleBar.Height / 2) - (s.Height / 2) - 

    // Draw a rectangle around the entire Form

    g.DrawRectangle(Pens.Black, ptOffset.X, 
       ptOffset.Y - rTitleBar.Height, _frm.Width, 
       _frm.Height + rTitleBar.Height);

The bulk of the code in this method prints the parts of the Form itself -- the title bar, the Form's window, and the title bar buttons (maximize, minimize, and close). An important point is that the parts are drawn in a certain order. The main form is the bottom most control and should be drawn first. All controls contained in the Form are drawn next using the DrawControl method (described below). Then the title bar and its buttons. All these Form related pieces are purely for an accurate printout and may be excluded for simplicity.

private void DrawControl(Control ctl, Point ptOffset, Graphics g)
    // Cycle through each control on the form and paint

    // it on the graphics object

    foreach (Control c in ctl.Controls)
        // Skip invisible controls

        if (!c.Visible)

        // Calculate the location offset for the control - this offset is

        // relative to the original offset passed in

        Point p = new Point(c.Left, c.Top);
        p.Offset(ptOffset.X, ptOffset.Y);

        // Draw the control

        if (c is GroupBox)
            DrawGroupBox((GroupBox)c, p, g);
        else if (c is Button)
            DrawButton((Button)c, p, g);
        else if (c is TextBox)
            DrawTextBox((TextBox)c, p, g);
        else if (c is CheckBox)
            DrawCheckBox((CheckBox)c, p, g);
        else if (c is Label)
            DrawLabel((Label)c, p, g);
        else if (c is ComboBox)
            DrawComboBox((ComboBox)c, p, g);
        else if (c is RadioButton)
            DrawRadioButton((RadioButton)c, p, g);

        // Draw the controls within this control

        DrawControl(c, p, g);

This method is the "meat & potatoes" of the project. It draws the contents of the specified Control on the specified Graphics object at the specified offset. This is where you'd add a couple lines of code to draw additional controls. The magic of this method lies in recursion. Note that it calls itself after the current control is drawn. This way, child controls are drawn on top.

Let's take a peek at one of the methods used to draw a particular control. I'll pick the DrawRadioButton as an example:

private void DrawRadioButton(RadioButton rdo, Point p, Graphics g)
    // Setup the size of a RadioButton

    Rectangle rRadioButton = new Rectangle(p.X, p.Y, 12, 12);

    ControlPaint.DrawRadioButton(g, p.X,
        p.Y + (rdo.Height / 2) - (rRadioButton.Height / 2),
        (rdo.Checked ? ButtonState.Checked : ButtonState.Normal));

    // RadioButton's text left justified & centered vertically

        new SolidBrush(rdo.ForeColor),
        rRadioButton.Right + 1,
        p.Y + (rdo.Height / 2) - (g.MeasureString(rdo.Text, 
            rdo.Font).Height / 2));

In this case, the Framework already provides a method to draw the Radio Button itself within the ControlPaint object. We have to provide the size and location of the Radio Button. Using MSPaint, I determined that a standard Radio Button is 12x12, which is why I hard-coded it here. After the Radio Button itself is drawn, the text must be drawn. For simplicity, I assume the text should be left-justified and centered vertically.

It's a bit of work to draw a Form in this way, but if you don't want to use unmanaged code it's the only solution I've found for the .NET environment. In the end, it's up to you how accurately you want your forms to print.


I found it interesting that the framework provides a handful of methods to draw controls but leaves most of the work to the programmer. You must supply the size and location, and in some cases the state of the control. It doesn't simply allow you to pass the control itself. In retrospect, this was more than I had intended to tackle.


  • Version 1.0 - initial release


This article, along with any associated source code and files, is licensed under A Public Domain dedication


About the Author

Software Developer
United States United States
I started playing with C# in beta 3 and have since been working with mostly win forms.

I spend my winters trying to keep warm and my summers with the windows rolled down.

You may also be interested in...


Comments and Discussions

GeneralRe: form back ground image Pin
Jodoveepn9-Feb-07 15:27
memberJodoveepn9-Feb-07 15:27 
GeneralChange to landscape Pin
gimpyyyyyyy5-Jun-06 7:14
membergimpyyyyyyy5-Jun-06 7:14 
GeneralRe: Change to landscape Pin
GabrielP15-Jun-06 7:44
memberGabrielP15-Jun-06 7:44 
GeneralRe: Change to landscape Pin
duongthaiha17-Jul-07 15:27
memberduongthaiha17-Jul-07 15:27 
GeneralRe: Change to landscape Pin
JSK16-Aug-07 18:35
memberJSK16-Aug-07 18:35 
QuestionRe: Change to landscape Pin
ramesees15-Apr-08 4:42
memberramesees15-Apr-08 4:42 
GeneralRe: Change to landscape [modified] Pin
Jodoveepn16-Apr-08 10:02
memberJodoveepn16-Apr-08 10:02 
GeneralRe: Change to landscape Pin
ramesees16-Apr-08 23:46
memberramesees16-Apr-08 23:46 
Hi, thanks for the reply

I am having to investigate this as we have recently taken order of a new printer at work which prints out A5 pages at high speed. However, the pages must be fed into the printer using long edge feed, rather than the standard short edge feed.

As a result, the output from the printer has to be rotated through 90 degrees so that it is still displayed from top to bottom on the page the way it would be expected to if it were fed normally.

I have had limited success with this, through changing the GeneratePrintingOffsets method to flip the x and y co-ordinates around, but that doesnt seem to be the full extent of changes I need to make. I even tried using Matrix transformations if I am in landscape mode (a boolean flag I added) to see if that has any effect on the output ... sadly it didnt, or I did something wrong.

I understand this is a rather unusual request or requirement, but it cant be impossible to achieve as its a matter of figuring out the correct mathematical routines to apply to the existing printing code. I would imagine that I would have to draw the form first and then rotate it (kind of like how I would do it manually - ie by pen and paper)

That image canvas idea sounds interesting, how would I go about starting with something like that?

Questionhow would the code be for a panel? Pin
tamdhu30-May-06 10:16
membertamdhu30-May-06 10:16 
AnswerRe: how would the code be for a panel? Pin
tamdhu31-May-06 2:40
membertamdhu31-May-06 2:40 
GeneralRe: how would the code be for a panel? Pin
Jodoveepn31-May-06 7:26
memberJodoveepn31-May-06 7:26 
AnswerRe: how would the code be for a panel? Pin
RASHU201015-Mar-09 8:35
memberRASHU201015-Mar-09 8:35 
QuestionHow To print Image background Pin
luca brandani29-May-06 0:38
memberluca brandani29-May-06 0:38 
AnswerRe: How To print Image background Pin
Jodoveepn31-May-06 7:31
memberJodoveepn31-May-06 7:31 
GeneralRe: How To print Image background Pin
luca brandani6-Jun-06 3:40
memberluca brandani6-Jun-06 3:40 
GeneralUsing .Net components Pin
Umina B20-Mar-06 7:10
memberUmina B20-Mar-06 7:10 
NewsAdded some functionality... Pin
SteveFromFlorida13-Oct-05 9:30
memberSteveFromFlorida13-Oct-05 9:30 
GeneralRe: Added some functionality... Pin
Jodoveepn17-Oct-05 6:05
memberJodoveepn17-Oct-05 6:05 
GeneralMultiple Page Problems Pin
Anonymous10-Aug-05 16:27
sussAnonymous10-Aug-05 16:27 
GeneralRe: Multiple Page Problems Pin
Jodoveepn17-Oct-05 6:15
memberJodoveepn17-Oct-05 6:15 
GeneralPrinting multiple pages Pin
barneyh25-Jul-05 9:18
memberbarneyh25-Jul-05 9:18 
GeneralRe: Printing multiple pages Pin
Jodoveepn25-Jul-05 11:52
memberJodoveepn25-Jul-05 11:52 
QuestionRe: Printing multiple pages Pin
mateo1423-Jun-06 12:14
membermateo1423-Jun-06 12:14 
AnswerRe: Printing multiple pages Pin
Fatania Dipak9-Dec-06 1:35
memberFatania Dipak9-Dec-06 1:35 
QuestionHow will I make a list box work as a combo(Drop Down style) Pin
kellap21-May-05 6:53
memberkellap21-May-05 6:53 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.180111.1 | Last Updated 27 Nov 2008
Article Copyright 2004 by jodoveepn
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid