
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 = {
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?
|
|
 |
 | Doesn'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.
|
|
|
|
 |
|
 |
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?
|
|
|
|
 |
 | .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...
|
|
|
|
 |
 | how about 64bit... Member 843116 | 22:42 11 Mar '09 |
|
|
 |
|
 |
I'd expect it to just work. Have you tried it and it failed?
|
|
|
|
 |
 | Can 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!
|
|
|
|
 |
 | How 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.
Thanks, George
|
|
|
|
 |
 | Adding RTF text abrken | 23:17 1 Mar '07 |
|
 |
Hi,
You're code is great and works just fine (at least for me ).
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.
|
|
|
|
 |
|
 |
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);
|
|
|
|
 |
|
 |
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") {
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 so, reading the ITextServices documentation (yes, it IS documented ) 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
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.
|
|
|
|
 |
 | How 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)
|
|
|
|
 |
|
 |
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.
|
|
|
|
 |
|
 |
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 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)
|
|
|
|
 |
|
 |
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