Click here to Skip to main content
15,903,175 members
Articles / Programming Languages / C#
Article

Inserting images into a RichTextBox control (the OLE way)

Rate me:
Please Sign up or sign in to vote.
4.94/5 (22 votes)
1 Nov 20053 min read 540.6K   8.6K   103   97
This article shows how you can insert images, controls and ActiveX objects into a .NET RichTextBox control by using the OLE way. There are several samples about how it could be done, but all of them are in C++ and I needed it for managed code (C#).

Sample Image - MyExtRichTextBox.jpg

Introduction

This article shows how you can insert images, controls and ActiveX objects into a .NET RichTextBox control by using the OLE way, like explained in the Microsoft 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 a RichTextBox, 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 to Khendys Gordon for the article: "Insert Plain Text and Images into RichTextBox at Runtime" and John 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 from pinvoke.net.

The first step to insert an OLE object into a RichTextBox is to get its IRichEditOle interface. It could be done, by sending the message EM_GETOLEINTERFACE to the control:

C#
this.IRichEditOle = SendMessage(richEditHandle, EM_GETOLEINTERFACE, 0);

With this interface, you can insert objects through the REOBJECT struct. It is important to note the you can specify the insertion point, the aspect and the dwUser variable to store flags or any related information for this object, so you can recover it at any time for update.

C#
//-----------------------
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 interface IDataObject, in this case I named it myDataObject. This class is an OLE callback object that uses the FORMATETC and the STGMEDIUM 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).

C#
public class myDataObject : IDataObject
{
    private Bitmap mBitmap;
    public FORMATETC mpFormatetc;

    #region IDataObject Members

    private const uint S_OK = 0;
    private const uint E_POINTER = 0x80004003;
    private const uint E_NOTIMPL = 0x80004001;
    private const uint E_FAIL = 0x80004005;

    public uint 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;
    }

    ...

    #endregion

    public myDataObject()
    {
        mBitmap = new Bitmap(16, 16);
        mpFormatetc = new FORMATETC();
    }

    public void 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
        {
        }
    }

    public void 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 method SetImage creates a Bitmap object to use its handle when the GetData is called.

Now, here is how the object is inserted into the RichEditBox creating a shared global memory and getting a pointer to it (IStorage) and using the OleClientSite interface from the IRichEditOle.

C#
public void 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 method UpdateObjects. This method performs a search for all objects in the RichTextBox and if they are marked as special (in my case I use the dwUser variable), they will be updated:

C#
public void 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 normal RichTextBox into the form and then replace the type for MyExtRichTextBox:

C#
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 method UpdateObjects like this:

C#
private void 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 (obj is 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

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


Written By
Software Developer (Senior) Kinecor Ltee
Canada Canada
I have been working for 16 years as Analyst Programmer in several companies.

I love the Object Oriented Programming paradigm and now, I love C#. Currently, I works with X++ in Microsoft Dynamics AX systems.

Also, I like to perform my work by using methodologies like Rational Unified Process (RUP).

Please, take a look to my last project: Meetgate

Comments and Discussions

 
GeneralRe: Activating the control Pin
Oscar Londono18-Mar-06 4:44
Oscar Londono18-Mar-06 4:44 
GeneralRe: Activating the control Pin
Mihai Dobrescu3-May-06 9:34
Mihai Dobrescu3-May-06 9:34 
GeneralRe: Activating the control Pin
Anthony Queen22-Nov-06 9:30
Anthony Queen22-Nov-06 9:30 
GeneralMove objects inside Richtextbox using Mouse Pin
ApurvaShah14-Feb-06 18:14
ApurvaShah14-Feb-06 18:14 
GeneralRe: Move objects inside Richtextbox using Mouse Pin
Oscar Londono20-Feb-06 3:59
Oscar Londono20-Feb-06 3:59 
GeneralRe: Move objects inside Richtextbox using Mouse Pin
slatvik24-Jul-06 8:58
slatvik24-Jul-06 8:58 
GeneralOleCreateFromFile fails in VB translation - Addendum... Pin
J Whattam13-Feb-06 11:09
J Whattam13-Feb-06 11:09 
GeneralOleCreateFromFile fails in VB translation... Pin
J Whattam13-Feb-06 10:38
J Whattam13-Feb-06 10:38 
I was wondering if anyone has successfully translated this code to VB.NET. I have attempted it and the InsertControl method works without problems. When I try to implement the InsertImageFromFile method, it fails on OleCreateFromFile call - the pOleObjectOut parameter remains nothing and I get a E_NOINTERFACE error - if I remove the UnmanagedType.IUnknown attribute in the DLL declaration, I still get a E_NOINTERFACE error. Here is what I have so far:

Public Sub InsertImageFromFile(ByVal FileName As String)
Dim pResult As Integer = 0
Dim pLockBytes As ILockBytes = Nothing
Dim pStorage As IStorage = Nothing
Dim pOleClientSite As IOleClientSite = Nothing
Dim pFormatEtc As FormatEtc = New FormatEtc()
Dim pOleObject As IOleObject = Nothing
Dim pOleObjectOut As Object = Nothing

Try
' Allocate an array of locked bytes for storage of the object.
pResult = CreateILockBytesOnHGlobal(IntPtr.Zero, True, pLockBytes)
If (pResult <> S_OK) OrElse (pLockBytes Is Nothing) Then
Throw New Win32Exception("Unable to allocate array of locked bytes.")
End If

' Create and open a new compound file storage object on top of byte-array.
pResult = StgCreateDocfileOnILockBytes(pLockBytes, STGM.STGM_ShareExclusive Or STGM.STGM_Create Or STGM.STGM_ReadWrite, 0, pStorage)
If (pResult <> S_OK) OrElse (pStorage Is Nothing) Then
Throw New Win32Exception("Unable to create compound file storage object.")
End If

pResult = Me.RichEditOle.GetClientSite(pOleClientSite)
If (pResult <> S_OK) OrElse (pOleClientSite Is Nothing) Then
Throw New Win32Exception("Unable to get client site for new object.")
End If

With pFormatEtc
.cfFormat = ClipboardFormats.CF_Default
.ptd = IntPtr.Zero
.dwAspect = DVAspect.DVAspect_Content
.lindex = -1
.tymed = Tymed.Tymed_NULL
End With

' IID for IOleObject interface.
Dim pIID_IOleObject As Guid = New Guid("00000112-0000-0000-C000-000000000046")
Dim pCLSID_NULL As Guid = New Guid("00000000-0000-0000-0000-000000000000")
' ************ This is where it fails, pResult is E_NOINTERFACE ***********
pResult = OleCreateFromFile(pCLSID_NULL, FileName, pIID_IOleObject, OleRender.OleRender_Draw, pFormatEtc, pOleClientSite, pStorage, pOleObjectOut)
If (pResult <> S_OK) OrElse (pOleObjectOut Is Nothing) Then
MsgBox(CStr(pResult))
Throw New Win32Exception("Unable to create object.")
End If

' Convert returned IUnknown interface pointer back to a IOleObject pointer.
pOleObject = CType(pOleObjectOut, IOleObject)
Dim pGuid As Guid = New Guid()
pOleObject.GetUserClassID(pGuid)
OleSetContainedObject(pOleObject, True)
Dim pReObject As ReObject = New ReObject()
With pReObject
.cp = Me.TextLength
.clsid = pGuid
.pStg = pStorage
.pOleObj = Marshal.GetIUnknownForObject(pOleObject)
.pOleSite = pOleClientSite
.dvAspect = DVAspect.DVAspect_Content
.dwFlags = REObjectFlags.REO_BelowBaseLine
.dwUser = 0
End With
Me.RichEditOle.InsertObject(pReObject)
Finally
' Ensure all interface pointers are released.
If pLockBytes IsNot Nothing Then
Marshal.ReleaseComObject(pLockBytes)
End If
If pOleClientSite IsNot Nothing Then
Marshal.ReleaseComObject(pOleClientSite)
End If
If pStorage IsNot Nothing Then
Marshal.ReleaseComObject(pStorage)
End If
If pOleObject IsNot Nothing Then
Marshal.ReleaseComObject(pOleObject)
End If
End Try
End Sub


The DLL declaration I've been using is:
Friend Declare Auto Function OleCreateFromFile Lib "ole32.dll" (ByRef rclsid As Guid, <marshalas(unmanagedtype.lpwstr)> ByVal lpszFileName As String, ByRef riid As Guid, ByVal renderopt As OleRender, ByRef pFormatEtc As FormatEtc, ByVal pClientSite As IOleClientSite, ByVal pStg As IStorage, <out(), marshalas(unmanagedtype.iunknown)=""> ByRef ppvObj As Object) As Integer

As I said earlier, the code up to the OleCreateFromFile call appears to be working OK since it is identical to code in InsertControl method which works without problems.

Can anyone see something wrong with the above code? Any help or comments would be greatly appreciated.Sigh | :sigh:





John Whattam.
GeneralRe: OleCreateFromFile fails in VB translation... Pin
Oscar Londono14-Feb-06 2:39
Oscar Londono14-Feb-06 2:39 
GeneralRe: OleCreateFromFile fails in VB translation... Pin
J Whattam15-Feb-06 10:10
J Whattam15-Feb-06 10:10 
GeneralRe: OleCreateFromFile fails in VB translation... Pin
J Whattam7-Mar-06 10:28
J Whattam7-Mar-06 10:28 
GeneralRe: OleCreateFromFile fails in VB translation... Pin
J Whattam8-Mar-06 12:18
J Whattam8-Mar-06 12:18 
GeneralRe: OleCreateFromFile fails in VB translation... Pin
Oscar Londono9-Mar-06 2:20
Oscar Londono9-Mar-06 2:20 
GeneralAlpha Channel versus Image Name Pin
twesterd8-Feb-06 16:04
twesterd8-Feb-06 16:04 
GeneralRe: Alpha Channel versus Image Name Pin
Oscar Londono14-Feb-06 3:10
Oscar Londono14-Feb-06 3:10 
GeneralMove inside objects by Drag and Drop Effect Pin
ApurvaShah29-Dec-05 22:26
ApurvaShah29-Dec-05 22:26 
GeneralRe: Move inside objects by Drag and Drop Effect Pin
Member 161654211-Oct-13 21:50
Member 161654211-Oct-13 21:50 
QuestionHow do you get the context menu for the inserted object Pin
Patrick Blackman1-Nov-05 18:00
professionalPatrick Blackman1-Nov-05 18:00 
AnswerRe: How do you get the context menu for the inserted object Pin
Oscar Londono2-Nov-05 2:55
Oscar Londono2-Nov-05 2:55 
GeneralRe: How do you get the context menu for the inserted object Pin
slatvik24-Jul-06 9:48
slatvik24-Jul-06 9:48 

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.