Click here to Skip to main content
Email Password   helpLost your password?

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:

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

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?

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralDoesn't work if compiled with VST2005 SP1 on Vista
mhorowit
17:09 17 Jan '10  
The download example works If I don't compile it.
ITextServices::TxGetNaturalSize fails with a bad argument.
GeneralRe: Doesn't work if compiled with VST2005 SP1 on Vista
David Kinder
2:54 29 Jan '10  
I've just compiled it with VS 2005 SP1 on XP, and it compiles and runs fine. (A few compiler warnings, but nothing important.) I don't have Vista to test on.

You'll have to provide more detail with what happens on your system. Do you mean you get a compiler error, or some sort of run-time error? In either case, what is the exact error?
General.net code?
Roey C
12:38 19 Sep '09  
does any body know how to implement this under .net?

Don't believe to what you hear on the news...

Generalhow about 64bit...
Member 843116
22:42 11 Mar '09  
..
GeneralRe: how about 64bit...
David Kinder
4:32 20 Mar '09  
I'd expect it to just work. Have you tried it and it failed?
GeneralCan you help me?
kjsfuture
18:42 5 Aug '08  
Dear, David Kinder!
I have read your article about Windowless rich edit control.
Your article about it helped me largely.
I'd like to contact u with my personal matter.
recently, i'm learning macro language of excel .
here's my question.
i can edit text in excel's cell and textbox shape.
Suppose cell's wordwrap property is true and have multiline text in it.
do you think that i can get first line characters using Text Object Model(TOM).
i need your help.
expect your goodness help.
I need answers in it as quickly as possible.

Regard!
QuestionHow to use "TxGetNaturalSize" method?
george1128
17:01 16 Jun '08  
Hello

Could someone help me to use "TxGetNaturalSize" method? I create a button by using activeX control and I want put the text in center. I need to know the text height. Smile

Thanks,
George
GeneralAdding RTF text
abrken
23:17 1 Mar '07  
Hi,

You're code is great and works just fine (at least for me Wink ).

I have a request about RTF.

I have added a method to your class to load a file using m_TextDoc->Open and it works using rtf file

Now I want to be able to "settext" with an rtf string.

Do you have any idea on how to achive this ?

Thanks,

Alain.
GeneralRe: Adding RTF text
David Kinder
6:05 4 Mar '07  
That first VARIANT argument to ITextDocument::Open() can contain an IStream COM interface pointer, which is probably the easiest way to go. There's an undocumented MFC class called CStreamOnCString that is very handy for this sort of thing: you give it a string, and can then get an IStream pointer. Something like this should work (not tested, or even compiled, but hopefully along the right lines):

#include <afxhtml.h> // for CStreamOnCString
CStreamOnCString rtfText("My RTF text here ...");
m_TextDoc->Open(CComVariant(&rtfText),tomReadOnly|tomRTF,0);

GeneralRe: Adding RTF text
abrken
0:13 5 Mar '07  
Thanks David for your answer.

Unfortunately, I'm using VC6 and not VC8 so that CStreamOnCString is not supported.
I have made some try with an IStream before you answer but didn't succeed, so I have made a "return to the roots" with a simple clippboard :
if (csStr.Left(5) == "{\\rtf") {
// Assume it's an RTF well formated string
/* CLIPBOARD WAY
*/
if (::OpenClipboard(NULL) != FALSE) {
CComPtr range;
HANDLE hClipboardData;
LPBYTE lpb;
UINT uiRTF = ::RegisterClipboardFormat("Rich Text Format");

::EmptyClipboard();
hClipboardData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, csStr.GetLength());
lpb = (LPBYTE)GlobalLock(hClipboardData);
CopyMemory(lpb, csStr.operator LPCTSTR(), csStr.GetLength());
GlobalUnlock(hClipboardData);
::SetClipboardData(uiRTF, hClipboardData);
::CloseClipboard();
GlobalFree(hClipboardData);

if (m_pRichText != NULL)
delete m_pRichText;
m_pRichText = new CRichDrawText();
m_pRichText->Range(0,0,&range);
range->Paste(NULL,0);
m_bUseRtf = true;
}

This code is running quite good but didn't satisfied me Smile so, reading the ITextServices documentation (yes, it IS documented Wink ) I have noticed this method : TXSendMessage.

After you have answered me and realized that the CStreamOnCString wasn't available for me (VC6) I have made another modification to your class adding the method TXSendMessage to directly access to the m_TextServ->TxSendMessage.

Then I did use some code snippset from Johan Rosengren to implement the EM_STREAMIN message and this works fine
/* STREAMIN WAY
*/
if (m_pRichText != NULL)
delete m_pRichText;
m_pRichText = new CRichDrawText();


CString* str = new CString( csStr );

EDITSTREAM es;
LRESULT result;
es.dwCookie = ( DWORD ) str;
es.pfnCallback = StreamIn;
m_pRichText->TxSendMessage(EM_STREAMIN, WPARAM(SF_RTF), LPARAM(&es), &result );

delete str;

m_bUseRtf = true;

Anyway, Many thanks for your answer.

Alain.
GeneralHow to add scrolling support
Duc Truong
9:21 14 Oct '06  
Great article on an poorly documented feature of the RichEdit control.
I've been trying to put the Rich Edit control on a bitmap background like MS Money app without much success.
My method is to set the Rich Edit on Transparent mode and draw the background bitmap via message ON_ERASE_BACKGROUND. It works but it flickers like hell when the RichEdit control is being resized.
My next attempt is to use memory DC, paint the background bitmap 1st, and the bit blit the contents of Windowless edit control (in transparent mode) on top of it and then bit blit that memory DC into my control.
My question is to you is how to handling scrolling support when the contents of windowless RichEdit control exceeds the physcical space of container view. I'd appreciate any input/idea.
Thank you in advance,

-In programming you can do anything. Paul DiLascia


In programming you can do anything (by Paul DiLascia)

GeneralRe: How to add scrolling support
David Kinder
23:24 15 Oct '06  
The windowless rich edit control won't add scrollbars: you have to provide them yourself in the surrounding host object. Have a look at Microsoft's example code at http://support.microsoft.com/kb/270161/en-us, which has a more complete ITextHost.

From the look of that code, it seems like it's supposed to handle scrollbars if you change the style of the hosting window to add WS_VSCROLL, though trying it, it seems to not quite work. The idea is right, though: what should be needed is that your containing host window should have a scrollbar, which the windowless rich edit controls via calls to your ITextHost::TxSetScrollPos.
GeneralRe: How to add scrolling support
Duc Truong
9:25 18 Oct '06  
Thank you for the pointer David,
I understand that window style WS_VSCROLL needs to be added to my container view first. And then I must figure out a way to sync my container view's
scrollbar with the windowless scrollbar. I'm just wondering if anyone has attempted to solve this problem before. Any feedback will be appreciated.
BTW, I believe MS Messenger uses a windowless richedit to host the chat with scollbar support. If Microsoft can do it, then I/we can do it Smile
As soon as I implement the code succesfully, I will email the code to you for inclusion to your article.
In the mean time, if anyone beats me to it, please share the code so everybody can benefit from it.
Thank you again for submitting the article. It sure has inspired me to get back to my transparent RicheEdit project which I stopped working on years ago.

In programming you can do anything (by Paul DiLascia)

GeneralRe: How to add scrolling support
Amro Fawzy
1:00 26 Aug '07  
Hello,
see if my articl helps

http://www.codeproject.com/richedit/SemiRichEdit.asp

I can help to optimize the code to get even a better performance, but I think that version should work fine with no problems.


Last Updated 10 Oct 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010