How do I print the contents of a rich text control?


For some reason, people are really puzzled by rich edit printing. I’m no expert on printing, but even I was able to figure it out. The kernel is the EM_FORMATRANGE message. Each time you call it, a little bit more of the rich text control is printed, and the message returns the index of the first unprinted character, which you can pass back in to print the next chunk.

The rest is just setting up and tearing down.

BOOL PrintRTF(HWND hwnd, HDC hdc)
{
 int cxPhysOffset = GetDeviceCaps(hdc, PHYSICALOFFSETX);
 int cyPhysOffset = GetDeviceCaps(hdc, PHYSICALOFFSETY);
 int cxPhys = GetDeviceCaps(hdc, PHYSICALWIDTH);
 int cyPhys = GetDeviceCaps(hdc, PHYSICALHEIGHT);

 SendMessage(hwnd, EM_SETTARGETDEVICE, (WPARAM)hdc, cxPhys);
 FORMATRANGE fr;
 fr.hdc = hdc;
 fr.hdcTarget = hdc;
 fr.rc.left = cxPhysOffset;
 fr.rc.right = cxPhysOffset + cxPhys;
 fr.rc.top = cyPhysOffset;
 fr.rc.bottom = cyPhysOffset + cyPhys;

 SendMessage(hwnd, EM_SETSEL, 0, (LPARAM)-1);
 SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&fr.chrg);

 BOOL fSuccess = TRUE;
 while (fr.chrg.cpMin < fr.chrg.cpMax && fSuccess) {
  fSuccess = StartPage(hdc) > 0;
  if (!fSuccess) break;
  int cpMin = SendMessage(hwnd, EM_FORMATRANGE, TRUE, (LPARAM)&fr);
  if (cpMin <= fr.chrg.cpMin) {
   fSuccess = FALSE;
   break;
  }
  fr.chrg.cpMin = cpMin;
  fSuccess = EndPage(hdc) > 0;
 }

 SendMessage(hwnd, EM_FORMATRANGE, FALSE, 0);

 return fSuccess;
}

We start by getting the dimensions of the page and telling the rich edit control what we intend to render to by using the EM_SETTARGETDEVICE message. Next, we need to fill out our FORMATRANGE, which we do by specifying the HDC we are rendering to, as well as the paper dimensions. But what about the character range? We are lazy and let the rich edit control take care of it for us: We select all the text and then ask the rich edit control to tell us what we just selected, which comes back in the form of a CHARRANGE, which is exactly what we needed.

Next comes the printing loop. While there is still text to print (and we haven’t encountered an error), we start a new page, ask the rich edit control to render that page, remember where the next page should begin, and end the current page. There’s a little sanity check in there to make sure that the rich edit control made forward progress; if not, then we’ll end up in an infinite loop spewing out blank pages! (I have no idea whether this is theoretically possible, but I’m going to protect against it just the same.)

Once the printing loop is complete, we clean up by sending one last EM_FORMATRANGE message to tell the rich edit control that we’re all done and it can discard the information it cached.

We can take all the information we’ve learned over the past few days to make a simple “print RTF” program.

int CALLBACK
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
          LPTSTR lpCmdLine, int nShowCmd)
{
 LoadLibrary(TEXT("riched20.dll"));
 HWND hwndRTF = CreateWindow(RICHEDIT_CLASS, NULL,
                     ES_MULTILINE | WS_OVERLAPPEDWINDOW,
                     CW_USEDEFAULT, CW_USEDEFAULT,
                     CW_USEDEFAULT, CW_USEDEFAULT,
                     NULL, 0, 0, 0);
 if (hwndRTF) {
  SendMessage(hwndRTF, EM_EXLIMITTEXT, 0, -1);
  if (FillRichEditFromFile(hwndRTF, lpCmdLine)) {
   PRINTDLG pd = { sizeof(pd) };
   pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
   if (PrintDlg(&pd)) {
    DOCINFO di = { sizeof(di) };
    di.lpszDocName = TEXT("Sample Printout");
    if (StartDoc(pd.hDC, &di) > 0) {
     if (PrintRTF(hwndRTF, pd.hDC)) {
      EndDoc(pd.hDC);
     } else {
      AbortDoc(pd.hDC);
     }
    }
    GlobalFree(pd.hDevMode);
    GlobalFree(pd.hDevNames);
    DeleteDC(pd.hDC);
   }
  }
  DestroyWindow(hwndRTF);
 }
 return 0;
}

There’s not really much going on here; it’s all just glue and necessary typing.

We create a rich edit control and fill it with the file passed on the command line. We then ask the PrintDlg function to give us a DC to the user’s default printer. We give the document a title, start the document, print the rich text into the document, and then end the document (or abort it if something went wrong during printing). A little cleaning up, and we’re all done. A tiny program to print an arbitrary RTF document with no fanfare whatsoever.

See? It’s not so hard. Once you find EM_FORMATRANGE the rest is just doing the obvious.

Comments (5)
  1. jon says:

    It’s worth noting also that the RichEdit control doesn’t support WM_PRINTCLIENT, so EM_FORMATRANGE is the only way to approximate this behavior as well.

    [Um, WM_PRINTCLIENT wouldn’t help even if it were supported. How do you print a 50-page document with WM_PRINTCLIENT? -Raymond]
  2. Nathan says:

    I haven’t looked at printing in windows since win95, and back then it was non-trivial to figure out. While I comprehend the code you’ve posted, I see printing continues to be a non-trivial task. Maybe since you talk of graphics etc enough, the abstraction of the device context/DC means who cares if you’re drawing to the screen or printer, but to the rest of us rubes, it’s stupidly complex.

  3. jon says:

    WM_PRINTCLIENT has nothing to do with printing Raymond, I was just pointing out that if you do want to get the RichEdit control to render itself to an arbitrary DC, EM_FORMATRANGE is the only way to do it.

  4. Sean W. says:

    Despite the relatively large quantity of documentation on this topic, I think there are two reasons people have been confused.  (Note:  I write this as somebody who’s never needed to make a RichEdit control print, and never even thought to look up the documentation before:  So I’m genuinely approaching this as a newbie, an educated and smart newbie, but a newbie nonetheless.)

    First, I think people are confused because this is what MSDN says about EM_FORMATRANGE:

    "EM_FORMATRANGE Message"

    "The EM_FORMATRANGE message formats a range of text in a rich edit control for a specific device."

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/richedit/richeditcontrols/richeditcontrolreference/richeditmessages/em_formatrange.asp

    You don’t see the word "print" anywhere in its documentation, and the word "printer" only appears once, near the bottom.  Anybody naively searching MSDN for the word "print" or "printing" is going to have a hard time finding this.  Try it for yourself; type "site:msdn.microsoft.com print richedit" into Google and see how long it takes to find a meaningful page.  A more useful (and yes, redundant) opening sentence might’ve been:

    "The EM_FORMATRANGE message formats a range of text in a rich edit control for a specific device, most commonly to print a RichEdit control’s contents to a printer."

    A search engine can *find* that one since it has all the right keywords in all the right places, and 99% of the time that’s what people are gonna use EM_FORMATRANGE for anyway, so it’s not inaccurate documentation.

    The second reason that people probably have trouble is that for many users, EM_FORMATRANGE is probably overkill; there’s no such thing as a simple EM_PRINT message.  As you yourself demonstrated, it’s 30 lines of code to get the thing to print.  Admittedly, that’s not a lot, and the additional code offers flexibility, but if all you want is to simply dump the contents of a RichEdit control (which I think is what a lot of people want), you have either some work ahead of you to figure it out or a lot of copy+paste ahead of you — if you can find a good example.  Just like CreateWindow can’t throw up a window in one or two lines of code, EM_FORMATMESSAGE can’t print in one or two lines of code, and I think a lot of developers have come to expect common operations can be done in one or two lines of code these days.

    So that’s my two cents on reading the documentation and sample code now, but I could always be wrong.

  5. required says:

    I’ve implemented printing for Windows apps and it is, as my colleague put it, "eine sauerei" (a mess). I think he was talking about the problem generally, rather than my code, but…

    Anyway, definitely non-trivial, though I eventually knocked up a small framework which made things a lot easier for anyone following me.

Comments are closed.