How do I load an entire file into a rich text control?


To load an entire file into a rich text control, you can use the EM_STREAMIN message, which accepts an IStream of data all at once. Once you find the message, it's pretty straightforward how to use it, but I'll write out the code anyway;

DWORD CALLBACK EditStreamCallback(DWORD_PTR dwCookie, LPBYTE lpBuff,
                                  LONG cb, PLONG pcb)
{
 HANDLE hFile = (HANDLE)dwCookie;
 return !ReadFile(hFile, lpBuff, cb, (DWORD *)pcb, NULL);
}

BOOL FillRichEditFromFile(HWND hwnd, LPCTSTR pszFile)
{
 BOOL fSuccess = FALSE;
 HANDLE hFile = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
                           0, OPEN_EXISTING,
                           FILE_FLAG_SEQUENTIAL_SCAN, NULL);
 if (hFile != INVALID_HANDLE_VALUE) {
  EDITSTREAM es = { (DWORD_PTR)hFile, 0, EditStreamCallback };
  if (SendMessage(hwnd, EM_STREAMIN, SF_RTF, (LPARAM)&es) &&
      es.dwError == 0) {
   fSuccess = TRUE;
  }
  CloseHandle(hFile);
 }
 return fSuccess;
}

You pretty much follow your nose. The EM_STREAMIN message wants you to tell it the format of the stream (SF_RTF) and provide a pointer to an EDITSTREAM structure that controls the input. Since we want to read from a file, we open a file for reading and use it as the dwCookie for our EditStreamCallback. The only tricky part is getting the return value correct for the callback. For some reason, the rich edit control wants zero on success and nonzero on failure, so we need to flip the sense of the ReadFile return value accordingly. Aside from that, there's nothing particularly interesting going on.

"But I tried this, and only the first line of the file gets read in. What am I doing wrong?"

Ah, a classic rookie mistake. You forgot to set the ES_MULTILINE style when you created the rich edit control.

Don't worry, I made this mistake, too.

"What if my data is in some other format than a file?"

As long as you can write a function that produces the next few bytes of data, you can stream it into a rich edit control. For example, here's a version that loads an arbitrary IStream into a rich edit control:

DWORD CALLBACK EditStreamCallback(DWORD_PTR dwCookie, LPBYTE lpBuff,
                                  LONG cb, PLONG pcb)
{
 IStream *pstm = (IStream *)dwCookie;
 return FAILED(pstm->Read(lpBuff, cb, (ULONG*)pcb));
}

BOOL FillRichEditFromStream(HWND hwnd, IStream *pstm)
{
 BOOL fSuccess = FALSE;
 EDITSTREAM es = { (DWORD_PTR)pstm, 0, EditStreamCallback };
 if (SendMessage(hwnd, EM_STREAMIN, SF_RTF, (LPARAM)&es) &&
     es.dwError == 0) {
  fSuccess = TRUE;
 }
 return fSuccess;
}

There's still a bug in this code, however, and it's not where you expect it. We'll take another look next time.

Comments (5)
  1. kokorozashi says:

    First!

    The only way I could get RICHEDIT50W to interpret RTF was to stream it in via callback as you show here. However, earlier versions seem happy to interpret RTF if you just throw it at them in one big message.

  2. Sam Gentile says:

    Completely buried with two projects and the main one is going to CTP #2 at a major back in Paris/London

  3. Kalle Olavi Niemitalo says:

    If the rich edit control was created by a different thread, then SendMessage will pass the EM_STREAMIN to that thread, so the IStream could get called from the wrong apartment.

    On second thought, even if the control was created by the thread that calls SendMessage, does anything guarantee that the control will call EditStreamCallback on that thread?  I don’t see that specified in the documentation of EM_STREAMIN or EditStreamCallback; I may have missed a general rule somewhere.

  4. Norman Diamond says:

    > There’s still a bug in this code, however,

    > and it’s not where you expect it.

    That sounds like a challenge, and someone lent me a few spare minutes, so let’s see.

    (1)  MSDN says:

    *  The control continues to call the callback

    *  function until one of the following

    *  conditions occurs:

    […]

    *  or an invalid character in the read buffer

    […]

    Suppose the control says to read 10 bytes, there are 65 bytes remaining readable in the file, and the next 10 bytes consist of 9 single-byte characters plus the first half of 1 double-byte character.  The result will be an invalid character in the read buffer.

    (2)  This code has the CALLBACK macro on the definition of EditStreamCallback, but MSDN’s specification says not to.

    (3)  If the assumption that somethings never change might possibly be false in the future, if DWORD might no longer be close enough to the same as LONG or if a DWORD might change to a DWORD_PTR (as some did), then some of this code’s casts might break in the future.  The effect might be silent successful compilation but danger at runtime.

  5. You need to raise the ceiling.

Comments are closed.