
Introduction
This article shows how you can insert images, controls and ActiveX objects into a .NETRichTextBox
control by using the OLE way, like explained in theMicrosoft site. Unfortunately, it covers only the sample with a C++ source code, so I need to implement a similar solution in managed code (C#).
There are other related articles for inserting images and OLE objects into aRichTextBox
, but they are using RTF codes, and I need a more specialized control suitable to be used for chat and to provide a way to insert emoticons, progress bars and images, and finally, recover them by getting their OLE handles or any object attribute.
Special thanks toKhendys Gordon for the article: "Insert Plain Text and Images into RichTextBox at Runtime" andJohn Fisher for his article: "Use IRichEditOle from C#".
Background
To achieve the solution, I need to use the P/Invoke (Platform Invoke) methods. I got a lot of information frompinvoke.net.
The first step to insert an OLE object into aRichTextBox
is to get itsIRichEditOle
interface. It could be done, by sending the messageEM_GETOLEINTERFACE
to the control:
this.IRichEditOle = SendMessage(richEditHandle, EM_GETOLEINTERFACE,0);
With this interface, you can insert objects through theREOBJECT
struct
. It is important to note the you can specify the insertion point, the aspect and thedwUser
variable to store flags or any related information for this object, so you can recover it at any time for update.
//-----------------------REOBJECT reoObject=new REOBJECT();reoObject.cp =this._richEdit.TextLength;reoObject.clsid = guid;reoObject.pstg = pStorage;reoObject.poleobj = Marshal.GetIUnknownForObject(control);reoObject.polesite = pOleClientSite;reoObject.dvAspect = (uint)(DVASPECT.DVASPECT_CONTENT);reoObject.dwFlags = (uint)(REOOBJECTFLAGS.REO_BELOWBASELINE);reoObject.dwUser =1;this.IRicEditOle.InsertObject(reoObject);//-----------------------
For inserting images, you need to implement the interfaceIDataObject
, in this case I named itmyDataObject
. This class is an OLE callback object that uses theFORMATETC
and theSTGMEDIUM
structures to display the image, telling to the OLE container that this object is a GDI medium (TYMED_GDI
) with a bitmap clipboard format (CF_BITMAP
).
publicclass myDataObject : IDataObject{private Bitmap mBitmap;public FORMATETC mpFormatetc;#region IDataObject Membersprivateconstuint S_OK =0;privateconstuint E_POINTER = 0x80004003;privateconstuint E_NOTIMPL = 0x80004001;privateconstuint E_FAIL = 0x80004005;publicuint GetData(ref FORMATETC pFormatetc,ref STGMEDIUM pMedium) {IntPtr hDst = mBitmap.GetHbitmap(); pMedium.tymed = (int)TYMED.TYMED_GDI; pMedium.unionmember = hDst; pMedium.pUnkForRelease =IntPtr.Zero;return (uint)S_OK; } ...#endregionpublic myDataObject() { mBitmap =new Bitmap(16,16); mpFormatetc =new FORMATETC(); }publicvoid SetImage(string strFilename) {try { mBitmap = (Bitmap)Bitmap.FromFile(strFilename,true);// Clipboard format = CF_BITMAP mpFormatetc.cfFormat = CLIPFORMAT.CF_BITMAP;// Target Device = Screen mpFormatetc.ptd =IntPtr.Zero;// Level of detail = Full content mpFormatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;// Index = Not applicaple mpFormatetc.lindex = -1;// Storage medium = HBITMAP handle mpFormatetc.tymed = TYMED.TYMED_GDI; }catch { } }publicvoid SetImage(Image image) {try { mBitmap =new Bitmap(image);// Clipboard format = CF_BITMAP mpFormatetc.cfFormat = CLIPFORMAT.CF_BITMAP;// Target Device = Screen mpFormatetc.ptd =IntPtr.Zero;// Level of detail = Full content mpFormatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;// Index = Not applicaple mpFormatetc.lindex = -1;// Storage medium = HBITMAP handle mpFormatetc.tymed = TYMED.TYMED_GDI; }catch { } }}
Take a look at how the member methodSetImage
creates aBitmap
object to use its handle when theGetData
is called.
Now, here is how the object is inserted into theRichEditBox
creating a shared global memory and getting a pointer to it (IStorage
) and using theOleClientSite
interface from theIRichEditOle
.
publicvoid InsertMyDataObject(myDataObject mdo){if (mdo ==null)return;//----------------------- ILockBytes pLockBytes;int sc = CreateILockBytesOnHGlobal(IntPtr.Zero,true,out pLockBytes); IStorage pStorage; sc = StgCreateDocfileOnILockBytes(pLockBytes, (uint) (STGM.STGM_SHARE_EXCLUSIVE|STGM.STGM_CREATE| STGM.STGM_READWRITE),0,out pStorage); IOleClientSite pOleClientSite;this.IRichEditOle.GetClientSite(out pOleClientSite);//----------------------- Guid guid = Marshal.GenerateGuidForType(mdo.GetType()); Guid IID_IOleObject =new Guid("{00000112-0000-0000-C000-000000000046}"); Guid IID_IDataObject =new Guid("{0000010e-0000-0000-C000-000000000046}"); Guid IID_IUnknown =new Guid("{00000000-0000-0000-C000-000000000046}");object pOleObject;int hr = OleCreateStaticFromData(mdo,ref IID_IOleObject, (uint)OLERENDER.OLERENDER_FORMAT,ref mdo.mpFormatetc, pOleClientSite, pStorage,out pOleObject);if (pOleObject ==null)return;//-----------------------//----------------------- OleSetContainedObject(pOleObject,true); REOBJECT reoObject =new REOBJECT(); reoObject.cp =this._richEdit.TextLength; reoObject.clsid = guid; reoObject.pstg = pStorage; reoObject.poleobj = Marshal.GetIUnknownForObject(pOleObject); reoObject.polesite = pOleClientSite; reoObject.dvAspect = (uint)(DVASPECT.DVASPECT_CONTENT); reoObject.dwFlags = (uint)(REOOBJECTFLAGS.REO_BELOWBASELINE); reoObject.dwUser =0;this.IRichEditOle.InsertObject(reoObject);//-----------------------//----------------------- Marshal.ReleaseComObject(pLockBytes); Marshal.ReleaseComObject(pOleClientSite); Marshal.ReleaseComObject(pStorage); Marshal.ReleaseComObject(pOleObject);//-----------------------}
There are other methods to insert controls and ActiveX objects, they look very similar to the above method, so please review the source code.
Points of Interest
And finally, how are the controls updated?
This is the trick, you need to use a timer and call the methodUpdateObjects
. This method performs a search for all objects in theRichTextBox
and if they are marked as special (in my case I use thedwUser
variable), they will be updated:
publicvoid UpdateObjects(){int k =this.IRichEditOle.GetObjectCount();for (int i =0; i < k; i++) { REOBJECT reoObject =new REOBJECT();this.IRichEditOle.GetObject(i, reoObject, GETOBJECTOPTIONS.REO_GETOBJ_ALL_INTERFACES);if (reoObject.dwUser ==1) { Point pt =this._richEdit.GetPositionFromCharIndex(reoObject.cp); Rectangle rect =new Rectangle(pt, reoObject.sizel);this._richEdit.Invalidate(rect,false);// repaint } }}
There is a lot of work required for optimizing this control but for now, any suggestion is appreciated.
Using the code
To use the code, simply add a reference to the control, put a normalRichTextBox
into the form and then replace the type forMyExtRichTextBox
:
MyExtRichTextBox.MyExtRichTextBox richTextBox1;
Tip
I update objects by creating an array of controls (buttons and progress bars) and adding a timer to the form, then calling the methodUpdateObjects
like this:
privatevoid timer1_Tick(object sender, System.EventArgs e){for (int i =0; i < ar.Count; i++) { itimer++;if (itimer >100) itimer =0;object obj = ar[i];if (objis Button) { Button bt = (Button) obj;if (bt.Text !="Clicked") bt.Text ="button " + i.ToString() +" - " + itimer.ToString(); }else { ProgressBar pb = (ProgressBar) obj;if (pb.Value +1 >100) pb.Value =0; pb.Value = pb.Value +1; } } richTextBox1.UpdateObjects();}
History
- Version 1.0 - Nov. 1 / 2005