A printable extension to .NET's RichTextBox

Environment:

VS .NET (works with RC1, should work with BETA2 as well)

Description:

With VC++ 6.0 everything was simple. You had a wizard create a project using a document/view architecture and chose CRichEditView as view class and MFC framework gave you print preview and printing out of the box without need to put additional work in it.

Then came VS.NET. Admittedly, the new features of the dotnet framework are great, but up until now it lacked an easy way of bringing the contents of a richedit control to your printer.

After many hours of searching for a suitable way to incorporate printing services into my project I finally started resorting to Win32 API calls again, since none of the suggestions you find on the web or on MSDN delivered anything practical.

After many more hours of fiddling with device contexts, page margins etc. I finally succeeded in creating an extension for .NET's RichTextBox control with additional methods for doing print output in a way it should have been included with .NET from the beginning (IMHO).

Coding:

In fact the only API function I had to encapsulate was to send EM_FORMATRANGE to the control. Since this particular message receives a pointer to a struct as one parameter I had to dwell into the depths of .NET's InteropServices, but in the end it worked out quite nicely.

First the required structs had to be introduced to .NET:

typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

translated to

[ StructLayout( LayoutKind.Sequential )]
private struct RECT 
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

Note that API's LONG type maps to an int in .NET, not to long or you'll get some weird results.

The FORMATRANGE struct handled much the same way, marshalling the device context handles to IntPtr.

Next came the declaration of the SendMessage API function:

[DllImport("user32.dll", CharSet=CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd,
                                         int msg,
                                         IntPtr wParam,
                                         IntPtr lParam);

Although you'll find an example in MSDN where SendMessage receives int as type for wParam and lParam, you need IntPtr here because a pointer to a FORMATRANGE struct has to be marshalled.

The rest went quite straightforward. To put the contents of the FORMATRANGE struct into an IntPtr parameter you have to use a static method

Marshal.StructureToPtr()
after allocating enough space with
Marshal.AllocCoTaskMem()
Don't forget to release the memory afterwards by calling
Marshal.FreeCoTaskMem()
(the Garbage Collector won't help you here).

Usage:

Use RichTextBoxEx the same way you would use a regular RichTextBox.

When it comes to printing, you can use the method FormatRange() to render the contents of your RichTextBoxEx control according to the PrintPageEventArgs parameter you receive in the PrintPageEvent event handler function.

When you're done printing call the method FormatRangeDone() to release any cached information the WIN32 RichEdit control still is holding.

Have a look at the sample project if you're unsure on how to use the control.

Downloads

Download demo project - 7 Kb
Download source (RichTextBox assembly and demo project) - 15 Kb