Tech Blech

Thursday, May 20, 2010

The challenge of printing a .NET textbox

On Yahoo Answers, someone asked how to print the content of a RichTextBox control in .NET. I did finally manage to print a (simpler) TextBox control in .NET, and that was difficult enough. I am documenting that here for myself or anyone else
facing this challenge.

First, a little rant. Before the .NET framework was released (~2000), Microsoft provided a handy .print method on the RichTextBox and TextBox controls, and all a programmer needed to do was call it. But in Microsoft's almighty wisdom (and trying to be just like Java), the simple and highly useful .print method was removed in .NET, and now you have to do all the following steps successfully. And note, it's just as difficult in Java--what were the language developers thinking? I imagine they were thinking to provide maximum flexibility, but why not provide a quick-and-dirty out for the rest of us?

In the example below, my form (called JobForm) is printing the contents of a TextBox control (called textBoxRight).

STEP 1: DECLARE VARIABLES

You need a bunch of special fields in your form code to keep track of printing information. To get started, just use this:

#region printing declarations

        private Font midlistRegular = new Font(
            "San Serif",
           (float)7.8,
           FontStyle.Regular,
           GraphicsUnit.Point);

        private Font midlistRegular1 = new Font(
            "San Serif",
           (float)7.6,
           FontStyle.Bold,
           GraphicsUnit.Point);

        private int printMargin = 1;

        private int lastPosition = 0;

        private int lastIndex = 0;

        /// 
        /// Set in Paint (of form) for use when printing
        /// 
        private float screenResolutionX = 0;

        #endregion printing declarations

STEP 2: INSTANTIATE A PRINTDOCUMENT OBJECT AND CREATE ITS EVENT CODE

You need a special object that raises an event each time one page has been printed and causes the next page to be printed. It is a non-visible control and is called "PrintDocument" (in library System.Drawing.Printing).

In the Windows Designer, drag a non-visible "PrintDocument" control onto your form (System.Drawing.Printing.PrintDocument). It will be instantiated on your form as "printDocument1". Double-click the PrintDocument control on the form to create the "PrintPage" event and give it the following code (using your TextBox name instead off "textBoxRight"):

private void printDocument1_PrintPage(object sender, PrintPageEventArgs e)
        {
            try
            {
                e.HasMorePages = this.PrintOnePage(
                    e.Graphics,
                    this.textBoxRight,
                    this.printDocument1,
                    this.screenResolutionX);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

STEP 3:

Now create the PrintOnePage() method needed by the above code. Although the display truncates this code, if you copy it using Control-C, all the code will be grabbed. Use the boilerplate code below unchanged (and I apologize that it's so ugly):

/// 
        /// Goes through each line of the text box and prints it
        /// 
        private bool PrintOnePage(Graphics g, 
                 TextBox txtSurface, PrintDocument printer, 
                 float screenResolution)
        {
            // Font textFont = txtSurface.Font;
            Font textFont = 
                   new Font("San serif", 
                            (float)10.0, FontStyle.Regular, 
                            GraphicsUnit.Point);

            // go line by line and draw each string
            int startIndex = this.lastIndex;
            int index = txtSurface.Text.IndexOf("\n", startIndex);

            int nextPosition = (int)this.lastPosition;
            // just use the default string format
            StringFormat sf = new StringFormat();

            // sf.FormatFlags = StringFormatFlags.NoClip | (~StringFormatFlags.NoWrap );
            // get the page height
            int lastPagePosition = (int)(((printer.DefaultPageSettings.PaperSize.Height / 100.0f) - 1.0f) * (float)screenResolution);
            // int resolution = printer.DefaultPageSettings.PrinterResolution.X;

            // use the screen resolution for measuring the page
            int resolution = (int)screenResolution;

            // calculate the maximum width in inches from the default paper size and the margin
            int maxwidth =
                (int)((printer.DefaultPageSettings.PaperSize.Width / 100.0f - this.printMargin * 2) * resolution);

            // get the margin in inches
            int printMarginInPixels = resolution * this.printMargin + 6;
            Rectangle rtLayout = new Rectangle(0, 0, 0, 0);
            int lineheight = 0;

            while (index != -1)
            {
                string nextLine = txtSurface.Text.Substring(startIndex, index - startIndex);
                lineheight = (int)(g.MeasureString(nextLine, textFont, maxwidth, sf).Height);
                rtLayout = new Rectangle(printMarginInPixels, nextPosition, maxwidth, lineheight);
                g.DrawString(nextLine, textFont, Brushes.Black, rtLayout, sf);

                nextPosition += (int)(lineheight + 3);
                startIndex = index + 1;
                index = txtSurface.Text.IndexOf("\n", startIndex);
                if (nextPosition > lastPagePosition)
                {
                    this.lastPosition = (int)screenResolution;
                    this.lastIndex = index;
                    return true; // reached end of page
                }
            }

            // draw the last line
            string lastLine = txtSurface.Text.Substring(startIndex);
            lineheight = (int)(g.MeasureString(lastLine, textFont, maxwidth, sf).Height);
            rtLayout = new Rectangle(printMarginInPixels, nextPosition, maxwidth, lineheight);
            g.DrawString(lastLine, textFont, Brushes.Black, rtLayout, sf);

            this.lastPosition = (int)screenResolution;
            this.lastIndex = 0;
            return false;
        }

STEP 4: ADD CODE TO YOUR FORM'S PAINT EVENT

In Windows Designer, open your form in graphical view mode. Open the form's Properties Window and click the lightning bolt to see events. Double-click on the form's Paint event to create it, and paste the boiler-plate code from my Paint event below into your form's paint event (your event will have a different name, using your form's name, than mine does below):

private void JobForm_Paint(object sender,
                            System.Windows.Forms.PaintEventArgs e)
        {
            // save the form height here
            this.screenResolutionX = e.Graphics.DpiX;

            // set the last position of the text box
            this.lastPosition = (int)this.screenResolutionX;
        }

STEP 5: ACTUALLY PRINT THE TEXTBOX

To actually print the contents of the TextBox, you'll need code like this in a print menu or button event:

PrintDialog printDialog1 = null;
 printDialog1 = new PrintDialog();
        if (printDialog1.ShowDialog() == DialogResult.OK)
        {
             this.printDocument1.PrinterSettings = printDialog1.PrinterSettings;
             this.printDocument1.Print();
        }

I haven't paid huge attention to all the above code, once I got it working. I snarfed much of it from various sources on the web (thank you, bloggers!). It could be enhanced or cleaned up a lot. Maybe this will help another programmer get it done.

Labels: ,

0 Comments:

Post a Comment

<< Home