Printing RichTextBox contents in C#

10. July 2009 05:54 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

This post discusses and provides the implementation of a helper class to add support
for printing the formatted contents of a richtextbox control.

The print model in .NET is a little different than one used in VB6.

For example see: http://support.microsoft.com/kb/146022

Public Sub PrintRTF(RTF As RichTextBox, LeftMarginWidth As Long, _
   TopMarginHeight, RightMarginWidth, BottomMarginHeight)
   Dim LeftOffset As Long, TopOffset As Long
   Dim LeftMargin As Long, TopMargin As Long
   Dim RightMargin As Long, BottomMargin As Long
   Dim fr As FormatRange
   Dim rcDrawTo As Rect
   Dim rcPage As Rect
   Dim TextLength As Long
   Dim NextCharPosition As Long
   Dim r As Long

   ' Start a print job to get a valid Printer.hDC
   Printer.Print Space(1)
   Printer.ScaleMode = vbTwips

   ' Get the offsett to the printable area on the page in twips
   LeftOffset = Printer.ScaleX(GetDeviceCaps(Printer.hdc, _
      PHYSICALOFFSETX), vbPixels, vbTwips)
   TopOffset = Printer.ScaleY(GetDeviceCaps(Printer.hdc, _
      PHYSICALOFFSETY), vbPixels, vbTwips)

   ' Calculate the Left, Top, Right, and Bottom margins
   LeftMargin = LeftMarginWidth - LeftOffset
   TopMargin = TopMarginHeight - TopOffset
   RightMargin = (Printer.Width - RightMarginWidth) - LeftOffset
   BottomMargin = (Printer.Height - BottomMarginHeight) - TopOffset

   ' Set printable area rect
   rcPage.Left = 0
   rcPage.Top = 0
   rcPage.Right = Printer.ScaleWidth
   rcPage.Bottom = Printer.ScaleHeight

   ' Set rect in which to print (relative to printable area)
   rcDrawTo.Left = LeftMargin
   rcDrawTo.Top = TopMargin
   rcDrawTo.Right = RightMargin
   rcDrawTo.Bottom = BottomMargin

   ' Set up the print instructions
   fr.hdc = Printer.hdc   ' Use the same DC for measuring and rendering
   fr.hdcTarget = Printer.hdc  ' Point at printer hDC
   fr.rc = rcDrawTo            ' Indicate the area on page to draw to
   fr.rcPage = rcPage          ' Indicate entire size of page
   fr.chrg.cpMin = 0           ' Indicate start of text through
   fr.chrg.cpMax = -1          ' end of the text

   ' Get length of text in RTF
   TextLength = Len(RTF.Text)

   ' Loop printing each page until done
   Do
      ' Print the page by sending EM_FORMATRANGE message
      NextCharPosition = SendMessage(RTF.hWnd, EM_FORMATRANGE, True, fr)
      If NextCharPosition >= TextLength Then Exit Do  'If done then exit
      fr.chrg.cpMin = NextCharPosition ' Starting position for next page
      Printer.NewPage                  ' Move on to next page
      Printer.Print Space(1) ' Re-initialize hDC
      fr.hdc = Printer.hdc
      fr.hdcTarget = Printer.hdc
   Loop

   ' Commit the print job
   Printer.EndDoc

   ' Allow the RTF to free up memory
   r = SendMessage(RTF.hWnd, EM_FORMATRANGE, False, ByVal CLng(0))
End Sub

The VBCompanion provides excellent helpers that provide a lot fo the VB6 Printer object functionality, so you dont have to change any of your actual code, but in some cases, you might just want to remove that code, specially for very specific things like printing a RichTextBox.

So here I’m providing a .NET simplified helper that allows you to print the contents of a RichTextBox control. This helper is just based on the code published by Martin Muller in http://msdn.microsoft.com/en-us/library/ms996492.aspx. It provides an extension method for VS 2008 user so all you have to do is call RichTextBox.Print.

The implementation is simple. The RichTextBoxPrintHelper creates or receives an instance of a PrintDocument object, and event handlers are added to it for the BeginPrint, PrintPage and EndPrint events.

    private int m_nFirstCharOnPage;

    private void printDocument_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        // Start at the beginning of the text
        m_nFirstCharOnPage = 0;
    }

    private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
        // To print the boundaries of the current page margins
        // uncomment the next line:
        //e.Graphics.DrawRectangle(System.Drawing.Pens.Blue, e.MarginBounds);

        // make the RichTextBoxEx calculate and render as much text as will
        // fit on the page and remember the last character printed for the
        // beginning of the next page
        m_nFirstCharOnPage = FormatRange(false,
            e,
            m_nFirstCharOnPage,
            control.TextLength);

        // check if there are more pages to print
        if (m_nFirstCharOnPage < control.TextLength)
            e.HasMorePages = true;
        else
            e.HasMorePages = false;
    }

    private void printDocument_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        // Clean up cached information
        FormatRangeDone();
    }
The FormatRange method is called. This method will use the fill out some structures 
with page information and use the RichTextBox handle to send messages that will render
the control contents to the Printer’s HDC.
 
 /// <summary>
    /// Calculate or render the contents of our RichTextBox for printing
    /// </summary>
    /// <param name="measureOnly">If true, only the calculation is performed,
    /// otherwise the text is rendered as well</param>
    /// <param name="e">The PrintPageEventArgs object from the
    /// PrintPage event</param>
    /// <param name="charFrom">Index of first character to be printed</param>
    /// <param name="charTo">Index of last character to be printed</param>
    /// <returns>(Index of last character that fitted on the
    /// page) + 1</returns>
    public int FormatRange(bool measureOnly, PrintPageEventArgs e,
        int charFrom, int charTo)
    {
        // Specify which characters to print
        STRUCT_CHARRANGE cr;
        cr.cpMin = charFrom;
        cr.cpMax = charTo;

        // Specify the area inside page margins
        STRUCT_RECT rc;
        rc.top = HundredthInchToTwips(e.MarginBounds.Top);
        rc.bottom = HundredthInchToTwips(e.MarginBounds.Bottom);
        rc.left = HundredthInchToTwips(e.MarginBounds.Left);
        rc.right = HundredthInchToTwips(e.MarginBounds.Right);

        // Specify the page area
        STRUCT_RECT rcPage;
        rcPage.top = HundredthInchToTwips(e.PageBounds.Top);
        rcPage.bottom = HundredthInchToTwips(e.PageBounds.Bottom);
        rcPage.left = HundredthInchToTwips(e.PageBounds.Left);
        rcPage.right = HundredthInchToTwips(e.PageBounds.Right);

        // Get device context of output device
        IntPtr hdc = e.Graphics.GetHdc();

        // Fill in the FORMATRANGE struct
        STRUCT_FORMATRANGE fr;
        fr.chrg = cr;
        fr.hdc = hdc;
        fr.hdcTarget = hdc;
        fr.rc = rc;
        fr.rcPage = rcPage;

        // Non-Zero wParam means render, Zero means measure
        Int32 wParam = (measureOnly ? 0 : 1);

        // Allocate memory for the FORMATRANGE struct and
        // copy the contents of our struct to this memory
        IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr));
        Marshal.StructureToPtr(fr, lParam, false);

        // Send the actual Win32 message
        int res = SendMessage(control.Handle, EM_FORMATRANGE, wParam, lParam);

        // Free allocated memory
        Marshal.FreeCoTaskMem(lParam);

        // and release the device context
        e.Graphics.ReleaseHdc(hdc);

        return res;
    }
 

Using the RichTextBox is even more simple. You add a richtextbox to a form and call the Print method:

image

        private void printToolStripMenuItem_Click(object sender, EventArgs e)
        {
            richTextBox1.Print();
        }

I’m attaching the source code for the helper an this sample application so you can use this.

DOWNLOAD SOURCE CODE