Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using Windowless RichEdit Controls

0.00/5 (No votes)
10 Oct 2006 1  
How to use the windowless RichEdit control, one of Microsoft's less well-documented APIs.

Sample Image

Introduction

Windowless controls have been around since the dawn of ActiveX, and are very useful when you do not want (or cannot have) a separate HWND for every control element in your user interface. This article presents the class CRichDrawText, a simple wrapper around the windowless version of the RichEdit control, allowing formatted text to be sized and drawn on a device context.

Background

As part of an application I was working on (the Windows version of Inform 7), I had a need to draw sections of formatted text in a CScrollView derived class. As the application was already making heavy use of the RichEdit control, that seemed the perfect control to use. Experiments with the FormatRange and DisplayBand functions in CRichEditCtrl were not satisfactory: what I needed was a proper windowless control.

When I actually came to try to use the windowless RichEdit control, however, life became more interesting. The MSDN documentation in this area is very sparse. Most of the useful information I found buried away in an MSDN Knowledge Base article, Q270161, with the remainder determined by trial and error. Hopefully, if you need a windowless RichEdit control, you won't have to do as much searching as I did.

Using the code

To use the class in your code, include RichDrawText.h and declare an instance of the CRichDrawText class somewhere.

To find out how much vertical space is needed for the contents of the CRichDrawText, call SizeText, passing in a device context and a rectangle whose width is the width you want the text to be formatted for. To actually draw the text, call DrawText, passing in a device context and the bounding rectangle.

To actually set up the formatted text, CRichDrawText provides two methods. SetText replaces all the text in the windowless control with the given Unicode string, and Range returns an ITextRange COM interface pointer for a range of text in the windowless control. ITextRange is part of the TOM (Text Object Model) which is an under-appreciated part of the RichEdit control, providing faster and more functional text manipulation than is available through the usual RichEdit EM_ messages. A description of the TOM is outside of the scope of this article, but the example program provided with this article, along with the MSDN documentation, should be enough to get you started.

The ITextHost implementation

In the CRichDrawText constructor, we create a windowless RichEdit control by calling the CreateTextServices function, to which we have to pass an implementation of the ITextHost interface. The CRichDrawText class provides a minimal implementation, which does the least possible to be able to size and draw the rich text. Which methods to implement was determined simply by seeing which were called using the debugger. Most of these methods are straightforward, however:

HRESULT CRichDrawText::XTextHost::TxNotify(DWORD iNotify, void *pv)
{
  return S_OK;
}
The TxNotify implementation must return S_OK, even if it does nothing with the notification messages it is passed. If it returns an error code then the sizing and drawing calls will fail.

Another catch for the unwary is TxGetCharFormat which must return a pointer to a CHARFORMATW structure, not a CHARFORMAT: the Unicode version of the structure must be used, even on Windows 95.

What CreateTextServices returns

Having successfully called CreateTextServices we now have an IUnknown COM pointer, which according to MSDN, we can now use QueryInterface on to get an ITextServices COM pointer. However, my initial attempts at this all failed: I always got E_NOINTERFACE back from the QueryInterface call.

The problem turns out to be that the IID_ITextServices linked in from riched20.lib is wrong. Thanks, Microsoft... The code linked from Knowledge Base article Q270161 contains the correct IID:

const IID IID_ITextServices = {
  // 8d33f740-cf58-11ce-a89d-00aa006cadc5

  0x8d33f740, 0xcf58, 0x11ce, {0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5}
};
When we include this in the CRichDrawText source file (where it will be linked in preference to the one in riched20.lib), we are able to get back an ITextServices COM pointer.

Calling ITextServices methods

Having implemented ITextHost and obtained an ITextServices interface, we are now ready to actually call the methods to size and draw text.

The CRichDrawText::SizeText method calls ITextServices::TxGetNaturalSize to calculate the required height. Trial and error has had to be used here to determine what all the arguments should be:

  • hicTargetDev and ptd are both NULL, even though the documentation does not say that null values are allowed. For drawing onto a display device context, it is not easy to see what else could be passed.
  • psizelExtent is a pointer to a SIZEL structure with both member variables set to -1. MSDN claims that this argument is "currently unused" but this seems not to be the case: passing in NULL leads to an access violation in riched20.dll. Passing in zeros (or small values) seems to affect the returned sizing rectangle. Given the lack of documentation, using {-1,-1} seems a reasonable approach.

The CRichDrawText::DrawText method calls ITextServices::TxDraw to draw the rich text. The arguments to this method have proved slightly less problematic. However:

  • pvAspect is NULL, though the documentation does not say if this is allowed. Given that the argument's type is void* and the documentation for it says "pointer for information for drawing optimizations", this might be worthy of some sort of award for unhelpful documentation.
  • hicTargetDev and ptd are again both NULL.
  • lViewId is zero. MSDN claims that this argument is not used, but in the Platform SDK TextServ.h defines enum TXTVIEW as "useful values for TxDraw lViewId parameter".

Conclusion

With poorly documented interfaces, the hardest problem is usually getting any of it to work in the first place. I hope that this article will give anyone playing with the windowless RichEdit control enough of a start that they can make it do what they need. Anyway, if it was easy, what would be the fun in that?

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here