Click here to Skip to main content
15,117,095 members
Articles / Programming Languages / C#
Article
Posted 11 Jul 2003

Stats

196.1K views
1.2K downloads
48 bookmarked

Use IRichEditOle from C#

Rate me:
Please Sign up or sign in to vote.
4.79/5 (25 votes)
13 Jul 2003CPOL5 min read
Take control of the OLE objects embedded inside a .NET RichTextBox

Sample Image - RichTextBoxPlus.jpg

Introduction

Recently, I needed to write some C# code to dynamically modify an object inside an RTF document.  I had done this in C++ before, so I knew it was possible, and started searching the internet for a boost (CP first!)

My hunt not only turned up fruitless, but there was outright misinformation out there.  Somewhere, one poster asked how to do this and got the response that it wasn't possible.  Now, that just got me all the more interested.  The die-hard, problem-solving coder in me just wouldn't let that one rest without a fight. 

So, here's the solution (and some interesting tidbits about the journey).

The problem

Basically, we want to be able to fill a RichTextBox with any RTF document, and then manipulate the objects within that document by using their COM interfaces.  If I were using MFC, I'd call the GetIRichEditOle function and start using the returned interface.  .NET has plenty of features which are already part of the RichTextBox class, but it doesn't provide a way to do that task.

Interop to the Rescue

There are plenty of articles on CodeProject about Interop for the .NET platform, so I won't go into much detail about that here.  However, this solution requires both platform invoke and COM Interop.

First, we have to fill in the gap missing in the RichTextBox control.  To accomplish this, we need to use the SendMessage Windows API call.  We'll use the EM_GETOLEINTERFACE message, which is specific to the RichEdit control.  Once we have the appropriate interop code setup (see the downloadable project), this can be done with the following code.  (Note that we need to be careful to handle the LPARAM parameter correctly.)

C#
 // Alloc a pointer to hold the return value for EM_GETOLEINTERFACE
 IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
 // Clear the pointer
 Marshal.WriteIntPtr(ptr, IntPtr.Zero); 
 try
 {
     if (0 != API.SendMessage(RichTextBox.Handle, 
                              Messages.EM_GETOLEINTERFACE, IntPtr.Zero, ptr))
     {
         // Read the returned pointer.  It is what we're looking for!
         IntPtr pRichEdit = Marshal.ReadIntPtr(ptr);
         try
         {
              // TODO: Do something with the pointer we just got...
         }
         finally
         {
             Marshal.Release(pRichEdit);
         }
     }
     else
     {
         throw new Exception("EM_GETOLEINTERFACE failed.");
     }
 }
 finally
 {
     // Free the ptr memory.
     Marshal.FreeCoTaskMem(ptr);
 }

Now that we have an IntPtr that is the IRichEditOle interface, we can do something with it. This is where the COM Interop stuff really kicks in.

Wrapping the IRichEditOle interface

To use the IRichEditOle interface we just obtained, we need some way for C# to call the methods on that interface.  Normally, we would find the dll from which that interface is defined, add it as a reference to the VS.NET project, and just start using the generated library.  That is either very difficult or not really possible with the information I've found, BUT we can accomplish the same task manually.

The following code is the C# representation of the IRichEditOle interface.  The ComImport, InterfaceType, and Guid attributes are needed in order for the interface to work, so don't leave them out if you're wrapping another interface in a similar fashion.  Browsing the C++ source code that is supplied with VS.NET is the best way to get this information.  (Note that I didn't test all of these methods, and didn't even bother trying to use the last few.  I'm focusing almost solely on the GetObject method.)

C#
 [ComImport]
 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 [Guid("00020D00-0000-0000-c000-000000000046")]
 public interface IRichEditOle
 {
  int GetClientSite(IntPtr lplpolesite);
  int GetObjectCount();
  int GetLinkCount();
  int GetObject(int iob, REOBJECT lpreobject, 
                         [MarshalAs(UnmanagedType.U4)]GetObjectOptions flags);
  int InsertObject(REOBJECT lpreobject);
  int ConvertObject(int iob, CLSID rclsidNew, string lpstrUserTypeNew);
  int ActivateAs(CLSID rclsid, CLSID rclsidAs);
  int SetHostNames(string lpstrContainerApp, string lpstrContainerObj);
  int SetLinkAvailable(int iob, int fAvailable);
  int SetDvaspect(int iob, uint dvaspect);
  int HandsOffStorage(int iob);
  int SaveCompleted(int iob, IntPtr lpstg);
  int InPlaceDeactivate();
  int ContextSensitiveHelp(int fEnterMode);
  //int GetClipboardData(CHARRANGE FAR * lpchrg, uint reco, 
  //                                                     IntPtr lplpdataobj);
  //int ImportDataObject(IntPtr lpdataobj, CLIPFORMAT cf, HGLOBAL hMetaPict);
 } 

Now, we have an interface!  But then what?  Well, since my application only needs the GetObject method, we've got to handle that REOBJECT class.  This is done with the following C# class (it could have been a structure, but then you'd need to pass it as a "ref" parameter to the GetObject function in IRichEditOle.)

C#
 [StructLayout(LayoutKind.Sequential)]
 public class REOBJECT
 {
  public REOBJECT()
  {
  }
  // Size of structure
  public int   cbStruct = Marshal.SizeOf(typeof(REOBJECT));  
  public int   cp = 0;                    // Character position of object
  public CLSID  clsid = new CLSID();      // Class ID of object
  public IntPtr  poleobj = IntPtr.Zero;   // OLE object interface
  public IntPtr  pstg = IntPtr.Zero;      // Associated storage interface
  public IntPtr  polesite = IntPtr.Zero;  // Associated client site interface
  public SIZEL  sizel = new SIZEL();      // Size of object (may be 0,0)
  public uint   dvaspect = 0;             // Display aspect to use
  public uint   dwFlags = 0;              // Object status flags
  public uint   dwUser = 0;               // Dword for user's use
 }

The CLSID member could be handled better, but I provided a structure with the necessary byte size to fill that gap.  We already know the CLSID, anyway.

Putting it all together

So, we have a way to get the IRichEditOle pointer value into an IntPtr variable and we have a C# interface that wraps IRichEditOle.  Now, we can fill in the gap in the first code section of this article.  Using GetTypedObjectForIUnknown, we can take that IntPtr and wrap it in our interface! But first, we need to make sure that we have the honest-to-goodness IRichEditOle interface pointer - not a pointer to another interface in that same COM object.  To do that, we'll use QueryInterface, supplying the GUID that we already used when wrapping the IRichEditOle interface above.

C#
 // Query for the IRichEditOle interface.
 Guid guid = new Guid("00020D00-0000-0000-c000-000000000046");
 Marshal.QueryInterface(pRichEdit, ref guid, out this.IRichEditOlePtr);
       
 // Wrap it in the C# interface for IRichEditOle.
 this.IRichEditOleValue = (IRichEditOle)Marshal.GetTypedObjectForIUnknown(
                                  this.IRichEditOlePtr, typeof(IRichEditOle));
 if (this.IRichEditOleValue == null)
 {
   throw new Exception("Failed to get the object wrapper for the interface.");
 }

Putting that all together into one function, we've got a nifty way to get the IRichEditOle pointer from a RichText Box. 

The code

To make it all nice and easy, I've derived a RichTextBoxPlus class from the RichTextBox class.  It is part of the RichTextBoxPlus.dll project, which also has the needed code for wrapping and using the IRichEditOle interface.  Add that dll as a reference to any project, then use RichTextBoxPlus in place of RichTextBox any time you need the IRichEditOle interface!

The demo project

There is a (lovely) demo application in the download, which loads an RTF document containing a Windows Media Player object.  (I tried to find an object that everyone would have access to.)  It then uses the IRichEditOle object to get an interface to WMPPlayer, and sets the URL to a MIDI file in the executable's folder.  The "Play song" menu lets you use the IRichEditOle interface to start the song, then switches to "Stop song" so you can call the stop function of the WMPPlayer object.  (If you want to use the buttons on Windows Media Player itself, you may need to double-click the object first.  This has something to do with the RTF control itself.)

If you have any questions, comments, or improvements, don't hesitate to contact me.

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

John Fisher
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
GeneralIdea!! Pin
Brisingr Aerowing21-Aug-10 9:11
professionalBrisingr Aerowing21-Aug-10 9:11 
GeneralDOC file interface inserted into a RichTextBox Pin
Ryan Ross25-Jul-09 5:05
MemberRyan Ross25-Jul-09 5:05 
GeneralRemoving Ole Objects from RTB Pin
The Innovator24-Mar-09 10:24
MemberThe Innovator24-Mar-09 10:24 
GeneralRe: Removing Ole Objects from RTB Pin
John Fisher24-Mar-09 17:38
MemberJohn Fisher24-Mar-09 17:38 
GeneralRe: Removing Ole Objects from RTB Pin
The Innovator24-Mar-09 18:27
MemberThe Innovator24-Mar-09 18:27 
QuestionIs it possible to activate powerpoint object embedded in richtextbox for editing in powerpoint instead of opening as slideshow Pin
mailtogj18-Mar-09 4:37
Membermailtogj18-Mar-09 4:37 
GeneralThanks Pin
Nicholas Butler22-Dec-08 0:14
sitebuilderNicholas Butler22-Dec-08 0:14 
QuestionInsert Object Dialog Box Pin
Chizbabaj10-Sep-08 23:07
MemberChizbabaj10-Sep-08 23:07 
AnswerRe: Insert Object Dialog Box Pin
John Fisher11-Sep-08 18:01
MemberJohn Fisher11-Sep-08 18:01 
It has been many years since I wrote this article. What little I once knew about the answer to your question resembles the hard dry chunk of unwrapped ancient cheese in the corner of your refrigerator. (Of course, I could do lots of research and experimentation to find a way to do this, but it would take enough time that we should probably work up some sort of contract first! Big Grin | :-D )

John

"You said a whole sentence with no words in it, and I understood you!" -- my wife as she cries about slowly becoming a geek.

GeneralRe: Insert Object Dialog Box Pin
Chizbabaj12-Sep-08 3:47
MemberChizbabaj12-Sep-08 3:47 
GeneralRe: Insert Object Dialog Box Pin
Aleksei Karimov15-Nov-08 0:35
MemberAleksei Karimov15-Nov-08 0:35 
GeneralStoring DOC files Pin
Steve Straley9-Aug-07 13:16
MemberSteve Straley9-Aug-07 13:16 
GeneralRe: Storing DOC files Pin
John Fisher9-Aug-07 17:48
MemberJohn Fisher9-Aug-07 17:48 
Generalplease reply:Having problem with add new property to reobject Pin
mohadese6-Aug-07 12:44
Membermohadese6-Aug-07 12:44 
GeneralRe: please reply:Having problem with add new property to reobject Pin
John Fisher6-Aug-07 17:16
MemberJohn Fisher6-Aug-07 17:16 
GeneralRe: please reply:Having problem with add new property to reobject Pin
mohadese6-Aug-07 22:57
Membermohadese6-Aug-07 22:57 
GeneralRe: please reply:Having problem with add new property to reobject Pin
John Fisher7-Aug-07 17:08
MemberJohn Fisher7-Aug-07 17:08 
GeneralRe: please reply:Having problem with add new property to reobject Pin
mohadese8-Aug-07 4:36
Membermohadese8-Aug-07 4:36 
GeneralRe: please reply:Having problem with add new property to reobject Pin
mohadese16-Aug-07 10:12
Membermohadese16-Aug-07 10:12 
GeneralRe: please reply! Pin
mohadese17-Aug-07 7:55
Membermohadese17-Aug-07 7:55 
GeneralOLE Object field of Access DB using C# Code to Read/Write Pin
Saghir Ahmed8-Nov-06 0:36
MemberSaghir Ahmed8-Nov-06 0:36 
Generalmisinformation Pin
Dragos Sbirlea13-Sep-06 22:38
MemberDragos Sbirlea13-Sep-06 22:38 
GeneralRe: misinformation Pin
John Fisher6-Aug-07 17:23
MemberJohn Fisher6-Aug-07 17:23 
QuestionHow to use Insert Object Pin
RakeshKG11-Sep-05 2:20
MemberRakeshKG11-Sep-05 2:20 
AnswerRe: How to use Insert Object Pin
Anthony Queen4-Dec-06 11:18
MemberAnthony Queen4-Dec-06 11:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.