Insert Image at Cursor Position in Rich Text box

10,823

Solution 1

I've prepared a fully functional example for you using the solution posted here exploiting the RTF power.

As Hans Passant wrote: the solution is quite tricky and there are some valid alternatives to achieve it.

BTW, this is your code (rewrited):

private bool CheckIfImage(string filename)
{
        var valids = new[] {".jpeg", ".jpg", ".png", ".ico", ".gif", ".bmp", ".emp", ".wmf", ".tiff"};
        return valids.Contains(System.IO.Path.GetExtension(filename));
}

private void openFileDialog2_FileOk(object sender, CancelEventArgs e)
{
    if (CheckIfImage(openFileDialog2.FileName.ToLower()) == true)
        embedImage(Image.FromFile(openFileDialog2.FileName));
    else
        MessageBox.Show("Invalid Image File Selected");
}

This is the embedImage method:

    private void embedImage(Image img)
    {
        var rtf = new StringBuilder();

        // Append the RTF header
        rtf.Append(@"{\rtf1\ansi\ansicpg1252\deff0\deflang1033");
        // Create the font table using the RichTextBox's current font and append
        // it to the RTF string
        rtf.Append(GetFontTable(this.Font));
        // Create the image control string and append it to the RTF string
        rtf.Append(GetImagePrefix(img));
        // Create the Windows Metafile and append its bytes in HEX format
        rtf.Append(getRtfImage(img));
        // Close the RTF image control string
        rtf.Append(@"}");
        richTextBox1.SelectedRtf = rtf.ToString();
    }

Here there are all the necessary methods:

    private enum EmfToWmfBitsFlags
    {
        EmfToWmfBitsFlagsDefault = 0x00000000,
        EmfToWmfBitsFlagsEmbedEmf = 0x00000001,
        EmfToWmfBitsFlagsIncludePlaceable = 0x00000002,
        EmfToWmfBitsFlagsNoXORClip = 0x00000004
    };

    private struct RtfFontFamilyDef
    {
        public const string Unknown = @"\fnil";
        public const string Roman = @"\froman";
        public const string Swiss = @"\fswiss";
        public const string Modern = @"\fmodern";
        public const string Script = @"\fscript";
        public const string Decor = @"\fdecor";
        public const string Technical = @"\ftech";
        public const string BiDirect = @"\fbidi";
    }

    [DllImport("gdiplus.dll")]
    private static extern uint GdipEmfToWmfBits(IntPtr _hEmf,
      uint _bufferSize, byte[] _buffer,
      int _mappingMode, EmfToWmfBitsFlags _flags);


    private string GetFontTable(Font font)
    {
        var fontTable = new StringBuilder();
        // Append table control string
        fontTable.Append(@"{\fonttbl{\f0");
        fontTable.Append(@"\");
        var rtfFontFamily = new HybridDictionary();
        rtfFontFamily.Add(FontFamily.GenericMonospace.Name, RtfFontFamilyDef.Modern);
        rtfFontFamily.Add(FontFamily.GenericSansSerif, RtfFontFamilyDef.Swiss);
        rtfFontFamily.Add(FontFamily.GenericSerif, RtfFontFamilyDef.Roman);
        rtfFontFamily.Add("UNKNOWN", RtfFontFamilyDef.Unknown);

        // If the font's family corresponds to an RTF family, append the
        // RTF family name, else, append the RTF for unknown font family.
        fontTable.Append(rtfFontFamily.Contains(font.FontFamily.Name) ? rtfFontFamily[font.FontFamily.Name] : rtfFontFamily["UNKNOWN"]);
        // \fcharset specifies the character set of a font in the font table.
        // 0 is for ANSI.
        fontTable.Append(@"\fcharset0 ");
        // Append the name of the font
        fontTable.Append(font.Name);
        // Close control string
        fontTable.Append(@";}}");
        return fontTable.ToString();
    }

    private string GetImagePrefix(Image _image)
    {
        float xDpi, yDpi;
        var rtf = new StringBuilder();
        using (Graphics graphics = CreateGraphics())
        {
            xDpi = graphics.DpiX;
            yDpi = graphics.DpiY;
        }
        // Calculate the current width of the image in (0.01)mm
        var picw = (int)Math.Round((_image.Width / xDpi) * 2540);
        // Calculate the current height of the image in (0.01)mm
        var pich = (int)Math.Round((_image.Height / yDpi) * 2540);
        // Calculate the target width of the image in twips
        var picwgoal = (int)Math.Round((_image.Width / xDpi) * 1440);
        // Calculate the target height of the image in twips
        var pichgoal = (int)Math.Round((_image.Height / yDpi) * 1440);
        // Append values to RTF string
        rtf.Append(@"{\pict\wmetafile8");
        rtf.Append(@"\picw");
        rtf.Append(picw);
        rtf.Append(@"\pich");
        rtf.Append(pich);
        rtf.Append(@"\picwgoal");
        rtf.Append(picwgoal);
        rtf.Append(@"\pichgoal");
        rtf.Append(pichgoal);
        rtf.Append(" ");

        return rtf.ToString();
    }

    private string getRtfImage(Image image)
    {
        // Used to store the enhanced metafile
        MemoryStream stream = null;
        // Used to create the metafile and draw the image
        Graphics graphics = null;
        // The enhanced metafile
        Metafile metaFile = null;
        try
        {
            var rtf = new StringBuilder();
            stream = new MemoryStream();
            // Get a graphics context from the RichTextBox
            using (graphics = CreateGraphics())
            {
                // Get the device context from the graphics context
                IntPtr hdc = graphics.GetHdc();
                // Create a new Enhanced Metafile from the device context
                metaFile = new Metafile(stream, hdc);
                // Release the device context
                graphics.ReleaseHdc(hdc);
            }

            // Get a graphics context from the Enhanced Metafile
            using (graphics = Graphics.FromImage(metaFile))
            {
                // Draw the image on the Enhanced Metafile
                graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height));
            }

            // Get the handle of the Enhanced Metafile
            IntPtr hEmf = metaFile.GetHenhmetafile();
            // A call to EmfToWmfBits with a null buffer return the size of the
            // buffer need to store the WMF bits.  Use this to get the buffer
            // size.
            uint bufferSize = GdipEmfToWmfBits(hEmf, 0, null, 8, EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
            // Create an array to hold the bits
            var buffer = new byte[bufferSize];
            // A call to EmfToWmfBits with a valid buffer copies the bits into the
            // buffer an returns the number of bits in the WMF.  
            uint _convertedSize = GdipEmfToWmfBits(hEmf, bufferSize, buffer, 8, EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
            // Append the bits to the RTF string
            foreach (byte t in buffer)
            {
                rtf.Append(String.Format("{0:X2}", t));
            }
            return rtf.ToString();
        }
        finally
        {
            if (graphics != null)
                graphics.Dispose();
            if (metaFile != null)
                metaFile.Dispose();
            if (stream != null)
                stream.Close();
        }
    }

I suggest you to wrap this into your own UserControl.

Solution 2

RichTextBox' support for OLE (Object Linking and Embedding) is an historical accident. OLE is a dead technology and has been heavily deprecated for many years now. It's death-knell certainly was .NET completely not supporting it. Removing OLE support from the native RichEdit control would have been wise but it would have broken too many ancient apps. The .NET RichTextBox class itself is just a small wrapper for the native component and doesn't add or subtract features from that component.

Accordingly, there is not any simple way to use the OLE api in .NET. The fact that copy/pasting through the clipboard still works is just an accident, .NET is not involved with that operation so is otherwise powerless to stop it.

So yes, it still works through the clipboard and is the only decent way to use the feature. There are certainly better alternatives, something like WebBrowser or Word interop gives you much more flexibility. PDF wrappers are popular. WPF supports compound documents well. Etcetera.

Share:
10,823
Admin
Author by

Admin

Updated on June 04, 2022

Comments

  • Admin
    Admin almost 2 years

    I know there are various questions like this but i'm asking because i couldn't understand all the answers give. I have RichTextBox and i want the user to be able to insert an image at the current cursor position.

    I have tried using the Clipboard to set the Image and then pasting it in the rich textbox. This works but i've been told its bad practice as it change data in a cliboard without notifying the user.

    This is what i have tried

        private bool CheckIfImage(string filename)
        {
            if (filename.EndsWith(".jpeg")) { return true; }
            else if (filename.EndsWith(".jpg")) { return true; }
            else if (filename.EndsWith(".png")) { return true; }
            else if (filename.EndsWith(".ico")) { return true; }
            else if (filename.EndsWith(".gif")) { return true; }
            else if (filename.EndsWith(".bmp")) { return true; }
            else if (filename.EndsWith(".emp")) { return true; }
            else if (filename.EndsWith(".wmf")) { return true; }
            else if (filename.EndsWith(".tiff")) { return true; }
            else { return false; }
        }
    
        private void openFileDialog2_FileOk(object sender, CancelEventArgs e)
        {
            if (CheckIfImage(openFileDialog2.FileName.ToLower()) == true)
            {
                Image img = Image.FromFile(openFileDialog2.FileName);
                string setData = (String)Clipboard.GetData(DataFormats.Rtf);
    
                Clipboard.SetImage(img);
                rtbType.Paste();
    
                Clipboard.SetData(DataFormats.Rtf, setData);
            }
            else
            {
                MessageBox.Show("Invalid Image File Selected");
            } 
        }
    

    Pls is there any better way to do this?