Wednesday, March 31, 2010

Printing and .NET

I needed to print a pattern to help mount a scope, which in turn made me learn .NET printing. As it turns out, it is really straightforward.

Here's an abbreviated guide.

First, add references to System.Drawing and System.Windows.Forms (the latter is for the printer selection dialog box).

Second, add the following usings:
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;

Third, subclass the PrintDocument class and override OnPrintPage:
class PrintSomething : PrintDocument
{
    protected override void OnPrintPage(PrintPageEventArgs e)
    {
        ...
    }
}

The available paper space is defined by DefaultPageSettings.PrintableArea which is a rectangle with the coordinate system at (Left, Top) and extending down to (Right, Bottom). The units are in 1/100th of an inch, and the drawing coordinates are also in the same units. So for example, to draw a one inch circle in the middle of the page...
float middleX = (DefaultPageSettings.PrintableArea.Right +
    DefaultPageSettings.PrintableArea.Left) / 2.0F;
int middleY = (DefaultPageSettings.PrintableArea.Bottom +
    DefaultPageSettings.PrintableArea.Top) / 2.0F;
Pen p = new Pen(Color.Black, 4);
e.Graphics.DrawEllipse(p, middleX - 50.0F, middleY + 50.0F, 100.0F, 100.0F);

Finally, the following code displays the printer selecting dialog and renders the picture if user clicks OK.
static void Main(string[] args)
{
    PrintSomething lines = new PrintSomething();
    PrintDialog dlg = new PrintDialog();
    dlg.Document = lines;
    if (dlg.ShowDialog() == DialogResult.OK)
        lines.Print();
}

So here is the final program:
//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="Sergey Solyanik">
// Copyright (C) Sergey Solyanik.
//
// This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
// </copyright>
//----------------------------------------------------------------------- 
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;

/// <summary>
/// This code prints a target that can be used for scope alignment.
/// </summary>
class PrintTarget : PrintDocument
{
    /// <summary>
    /// Prints out a target.
    /// </summary>
    /// <param name="args"> Arguments array. Not used. </param>
    static void Main(string[] args)
    {
        PrintTarget lines = new PrintTarget();
        PrintDialog dlg = new PrintDialog();
        dlg.Document = lines;
        if (dlg.ShowDialog() == DialogResult.OK)
            lines.Print();
    }

    /// <summary>
    /// Renders the page.
    /// </summary>
    /// <param name="e"> Printer parameters and graphics. </param>
    protected override void OnPrintPage(PrintPageEventArgs e)
    {
        Graphics g = e.Graphics;
        Pen p1 = new Pen(Color.Black, 0.5F);
        Pen p2 = new Pen(Color.Black, 1);
        Pen p4 = new Pen(Color.Black, 4);

        float step = 100.0F / 16.0F;

        int middleX = (int)((DefaultPageSettings.PrintableArea.Right -
            DefaultPageSettings.PrintableArea.Left) / step) / 2;
        int boundX = ((int)((DefaultPageSettings.PrintableArea.Right -
            DefaultPageSettings.PrintableArea.Left) / 200.0F)) * 16;
        int middleY = (int)((DefaultPageSettings.PrintableArea.Bottom -
            DefaultPageSettings.PrintableArea.Top) / step) / 2;
        int boundY = ((int)((DefaultPageSettings.PrintableArea.Bottom -
            DefaultPageSettings.PrintableArea.Top) / 200.0F)) * 16;

        float top = DefaultPageSettings.PrintableArea.Top +
            (middleY - boundY) * step;
        float bottom = DefaultPageSettings.PrintableArea.Top +
            (middleY + boundY) * step;
        float left = DefaultPageSettings.PrintableArea.Left +
            (middleX - boundX) * step;
        float right = DefaultPageSettings.PrintableArea.Left +
            (middleX + boundX) * step;
        for (int i = 0; i < boundX; ++i)
        {
            Pen p;
            if (i % 16 == 0)
                p = p4;
            else if (i % 8 == 0)
                p = p2;
            else
                p = p1;

            g.DrawLine(p,
                DefaultPageSettings.PrintableArea.Left +
                (middleX + i) * step,
                top,
                DefaultPageSettings.PrintableArea.Left +
                (middleX + i) * step,
                bottom);

            g.DrawLine(p,
                DefaultPageSettings.PrintableArea.Left +
                (middleX - i) * step,
                top,
                DefaultPageSettings.PrintableArea.Left +
                (middleX - i) * step,
                bottom);
        }

        for (int i = 0; i < boundY; ++i)
        {
            Pen p;
            if (i % 16 == 0)
                p = p4;
            else if (i % 8 == 0)
                p = p2;
            else
                p = p1;

            g.DrawLine(p,
                left,
                DefaultPageSettings.PrintableArea.Top +
                (middleY + i) * step,
                right,
                DefaultPageSettings.PrintableArea.Top +
                (middleY + i) * step);

            g.DrawLine(p,
                left,
                DefaultPageSettings.PrintableArea.Top +
                (middleY - i) * step,
                right,
                DefaultPageSettings.PrintableArea.Top +
                (middleY - i) * step);
        }

        for (int i = 1; i < 4; ++i)
        {
            g.DrawEllipse(p4,
                DefaultPageSettings.PrintableArea.Left +
                (middleX - 8 * i) * step,
                DefaultPageSettings.PrintableArea.Top +
                (middleY - 8 * i) * step,
                16 * i * step, 16 * i * step);
        }
    }
}

And here's what it prints:


Hint: Microsoft XPS document writer is a great way to debug printer output without wasting paper.

No comments: