Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF RichTextBox Insert Image and Undo Operation

I have code to import Images from Clipboard to RichtTextBox.

Image i = new Image();
i.Source =  Clipboard.GetImage();
paragraph.Inlines.Add(i);

When I try to delete image and press Undo() i have exception.

No matching constructor found on type 'System.Windows.Interop.InteropBitmap'. You can use the Arguments or FactoryMethod directives to construct this type.' Line number '1' and line position '226'.

This is because XAML generated by RichTextBox look like below:

<Image.Source><swi:InteropBitmap /></Image.Source>

I try to change type of BitmapSource to BitmapImage. But in this situation i have XAML:

<Image.Source><BitmapImage BaseUri="{x:Null}" /></Image.Source></Image>

And after delete,Undo i have exception:

Exception thrown: 'System.Windows.Markup.XamlParseException' in PresentationFramework.dll

Additional information: 'Initialization of 'System.Windows.Media.Imaging.BitmapImage' threw an exception.' Line number '1' and line position '243'.

I even try the InlineImage from: http://wpftutorial.net/InlineImagesXaml.html

<InlineImage Width="100" Height="100" Stretch="Fill">
   <![CDATA[iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAB3RJTUUH2AQP
        SFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAAnOSURBVHjaxVcLcBvVFT1vV
        ki3Hju3GCQnGjkObONQkJkxCSIHQQGnIdEr5TFs+LaGl7RRCSUvDp8nglH4mDGQ6EwZIm=]]>
    </InlineImage>

Even in this situation i have exception in Undo/Redo operation. Is there any possibility without writing own Undo/Redo operation to handle this situation.

like image 905
arkadykl Avatar asked Sep 14 '25 07:09

arkadykl


2 Answers

I am confused weather to edit old answer or add new one. So I am going for a new one.

Using below approach I can now copy an existing image file and paste it in RTB, and also I can now copy some unsaved image data from MSPaint, Photoshop and paste that too. After pressing Save button, rtf file is saved and opens nicely in MSWord as expected.

Ctrl+Z is not working, as image data is in stream. I am working on it. Ctrl+Z is not a problem when image is copied as file.

Any more doubts are most welcome. Code below is complete can be used as is.

ImageCode.cs for getting image stored in clipboard

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows;
using System.Runtime.InteropServices;

namespace WpfRichTextBox._32648134
{
    public class ImageCode
    {
        public static ImageSource ImageFromClipboardDibAsSource()
        {
            MemoryStream ms = Clipboard.GetData("DeviceIndependentBitmap") as MemoryStream;
            if (ms != null)
            {
                byte[] dibBuffer = new byte[ms.Length];
                ms.Read(dibBuffer, 0, dibBuffer.Length);

                BITMAPINFOHEADER infoHeader =
                    BinaryStructConverter.FromByteArray<BITMAPINFOHEADER>(dibBuffer);

                int fileHeaderSize = Marshal.SizeOf(typeof(BITMAPFILEHEADER));
                int infoHeaderSize = infoHeader.biSize;
                int fileSize = fileHeaderSize + infoHeader.biSize + infoHeader.biSizeImage;

                BITMAPFILEHEADER fileHeader = new BITMAPFILEHEADER();
                fileHeader.bfType = BITMAPFILEHEADER.BM;
                fileHeader.bfSize = fileSize;
                fileHeader.bfReserved1 = 0;
                fileHeader.bfReserved2 = 0;
                fileHeader.bfOffBits = fileHeaderSize + infoHeaderSize + infoHeader.biClrUsed * 4;

                byte[] fileHeaderBytes =
                    BinaryStructConverter.ToByteArray<BITMAPFILEHEADER>(fileHeader);

                MemoryStream msBitmap = new MemoryStream();
                msBitmap.Write(fileHeaderBytes, 0, fileHeaderSize);
                msBitmap.Write(dibBuffer, 0, dibBuffer.Length);
                msBitmap.Seek(0, SeekOrigin.Begin);

                BitmapImage img = new BitmapImage();
                img.BeginInit();
                img.CacheOption = BitmapCacheOption.OnDemand;
                img.CreateOptions = BitmapCreateOptions.DelayCreation;
                img.StreamSource = msBitmap;
                img.EndInit();

                return img;
            }
            return null;
        }

        public static MemoryStream ImageFromClipboardDibAsStream()
        {
            MemoryStream ms = Clipboard.GetData("DeviceIndependentBitmap") as MemoryStream;
            if (ms != null)
            {
                byte[] dibBuffer = new byte[ms.Length];
                ms.Read(dibBuffer, 0, dibBuffer.Length);

                BITMAPINFOHEADER infoHeader =
                    BinaryStructConverter.FromByteArray<BITMAPINFOHEADER>(dibBuffer);

                int fileHeaderSize = Marshal.SizeOf(typeof(BITMAPFILEHEADER));
                int infoHeaderSize = infoHeader.biSize;
                int fileSize = fileHeaderSize + infoHeader.biSize + infoHeader.biSizeImage;

                BITMAPFILEHEADER fileHeader = new BITMAPFILEHEADER();
                fileHeader.bfType = BITMAPFILEHEADER.BM;
                fileHeader.bfSize = fileSize;
                fileHeader.bfReserved1 = 0;
                fileHeader.bfReserved2 = 0;
                fileHeader.bfOffBits = fileHeaderSize + infoHeaderSize + infoHeader.biClrUsed * 4;

                byte[] fileHeaderBytes =
                    BinaryStructConverter.ToByteArray<BITMAPFILEHEADER>(fileHeader);

                MemoryStream msBitmap = new MemoryStream();
                msBitmap.Write(fileHeaderBytes, 0, fileHeaderSize);
                msBitmap.Write(dibBuffer, 0, dibBuffer.Length);
                msBitmap.Seek(0, SeekOrigin.Begin);

                return msBitmap;
            }
            return null;
        }
    }

    public static class BinaryStructConverter
    {
        public static T FromByteArray<T>(byte[] bytes) where T : struct
        {
            IntPtr ptr = IntPtr.Zero;
            try
            {
                int size = Marshal.SizeOf(typeof(T));
                ptr = Marshal.AllocHGlobal(size);
                Marshal.Copy(bytes, 0, ptr, size);
                object obj = Marshal.PtrToStructure(ptr, typeof(T));
                return (T)obj;
            }
            finally
            {
                if (ptr != IntPtr.Zero)
                    Marshal.FreeHGlobal(ptr);
            }
        }

        public static byte[] ToByteArray<T>(T obj) where T : struct
        {
            IntPtr ptr = IntPtr.Zero;
            try
            {
                int size = Marshal.SizeOf(typeof(T));
                ptr = Marshal.AllocHGlobal(size);
                Marshal.StructureToPtr(obj, ptr, true);
                byte[] bytes = new byte[size];
                Marshal.Copy(ptr, bytes, 0, size);
                return bytes;
            }
            finally
            {
                if (ptr != IntPtr.Zero)
                    Marshal.FreeHGlobal(ptr);
            }
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
     struct BITMAPFILEHEADER
    {
        public static readonly short BM = 0x4d42; // BM

        public short bfType;
        public int bfSize;
        public short bfReserved1;
        public short bfReserved2;
        public int bfOffBits;
    }

    [StructLayout(LayoutKind.Sequential)]
     struct BITMAPINFOHEADER
    {
        public int biSize;
        public int biWidth;
        public int biHeight;
        public short biPlanes;
        public short biBitCount;
        public int biCompression;
        public int biSizeImage;
        public int biXPelsPerMeter;
        public int biYPelsPerMeter;
        public int biClrUsed;
        public int biClrImportant;
    }

}

MainWindow.xaml

<Window x:Class="WpfRichTextBox._32648134.Win32648134"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Win32648134" Height="600" Width="700">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="41*"/>
            <RowDefinition Height="5*"/>
            <RowDefinition Height="21*"/>
        </Grid.RowDefinitions>
        <RichTextBox x:Name="RtbCompose" Width="500" Height="300" ScrollViewer.VerticalScrollBarVisibility="Visible">
            <FlowDocument x:Name="FdDocument">
                <Paragraph x:Name="Para1"></Paragraph>
            </FlowDocument>
        </RichTextBox>        
        <Button x:Name="BtnCopyImgFile"  Content="Paste image" HorizontalAlignment="Left" Margin="96,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="BtnCopyImgFile_Click"/>
        <Button x:Name="BtnSave" Content="Save" HorizontalAlignment="Left" Grid.Row="1" VerticalAlignment="Top" Width="75" Margin="521,10,0,0" Click="BtnSave_Click"/>
        <Button x:Name="BtnCopyImgData" Content="Paste image data" HorizontalAlignment="Left" Margin="190,11,0,0" Grid.Row="1" VerticalAlignment="Top" Click="BtnCopyImgData_Click"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Diagnostics;
using System.IO;

namespace WpfRichTextBox._32648134
{
    /// <summary>
    /// Interaction logic for Win32648134.xaml
    /// </summary>
    public partial class Win32648134 : Window
    {
        public Win32648134()
        {
            InitializeComponent();
        }

        private void BtnCopyImgFile_Click(object sender, RoutedEventArgs e)
        {              
            Image i = new Image();

            if (Clipboard.ContainsFileDropList())
            {
                StringCollection fileNames = Clipboard.GetFileDropList();
                BitmapImage img = new BitmapImage(new Uri(fileNames[0], UriKind.Absolute));
                i.Source = img;
                Para1.Inlines.Add(i);
            }     

            Para1.Inlines.Add(new Run("first rtb app"));
        }

        private void BtnSave_Click(object sender, RoutedEventArgs e)
        {
            TextRange allText = new TextRange(RtbCompose.Document.ContentStart, RtbCompose.Document.ContentEnd);

            FileStream stream = new FileStream(@"I:\RTB.rtf", FileMode.Create);

            allText.Save(stream, DataFormats.Rtf);

            if (stream != null)
                stream.Close();
        }

        private void BtnCopyImgData_Click(object sender, RoutedEventArgs e)
        {
            bool hasImgData = Clipboard.ContainsImage();
            Image i = new Image();
            if (hasImgData)
            {
                BitmapSource imgData = (BitmapSource)ImageCode.ImageFromClipboardDibAsSource();
                i.Source = imgData;

                Para1.Inlines.Add(i);
            }

            Para1.Inlines.Add(new Run("rtb app, image comes from image data instead of file"));
        }
    }
}
like image 186
AnjumSKhan Avatar answered Sep 17 '25 00:09

AnjumSKhan


I found the solution. I had to copy class WpfLoad from RichTextBox source code. This code save Package as Stream with Image as Content and Uri as Source of the document.

Because class WpfPayload is internal I don't have access to this class. I need to create my own.

Below is the source of the class WpfPayLoad.

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
using System.Windows.Media.Imaging;

namespace YATE
{
    internal class WpfPayload
    {
        private const string XamlPayloadDirectory = "/Xaml"; // 
        private const string XamlEntryName = "/Document.xaml"; // 
        private const string XamlContentType = "application/vnd.ms-wpf.xaml+xml";
        private const string XamlImageName = "/Image"; // 
        private const string XamlRelationshipFromPackageToEntryPart = "http://schemas.microsoft.com/wpf/2005/10/xaml/entry";
        private const string XamlRelationshipFromXamlPartToComponentPart = "http://schemas.microsoft.com/wpf/2005/10/xaml/component";

        internal const string ImageBmpContentType = "image/bmp";
        private const string ImageGifContentType = "image/gif";
        private const string ImageJpegContentType = "image/jpeg";
        private const string ImageTiffContentType = "image/tiff";
        private const string ImagePngContentType = "image/png";

        private const string ImageBmpFileExtension = ".bmp";
        private const string ImageGifFileExtension = ".gif";
        private const string ImageJpegFileExtension = ".jpeg";
        private const string ImageJpgFileExtension = ".jpg";
        private const string ImageTiffFileExtension = ".tiff";
        private const string ImagePngFileExtension = ".png";

        Package _package = null;

        private static BitmapEncoder GetBitmapEncoder(string imageContentType)
        {
            BitmapEncoder bitmapEncoder;

            switch (imageContentType)
            {
                case ImageBmpContentType:
                    bitmapEncoder = new BmpBitmapEncoder();
                    break;
                case ImageGifContentType:
                    bitmapEncoder = new GifBitmapEncoder();
                    break;
                case ImageJpegContentType:
                    bitmapEncoder = new JpegBitmapEncoder();
                    // 

                    break;
                case ImageTiffContentType:
                    bitmapEncoder = new TiffBitmapEncoder();
                    break;
                case ImagePngContentType:
                    bitmapEncoder = new PngBitmapEncoder();
                    break;
                default:
                    bitmapEncoder = null;
                    break;
            }
            return bitmapEncoder;
        }

        // Returns a file extension corresponding to a given imageContentType
        private static string GetImageFileExtension(string imageContentType)
        {
            string imageFileExtension;
            switch (imageContentType)
            {
                case ImageBmpContentType:
                    imageFileExtension = ImageBmpFileExtension;
                    break;
                case ImageGifContentType:
                    imageFileExtension = ImageGifFileExtension;
                    break;
                case ImageJpegContentType:
                    imageFileExtension = ImageJpegFileExtension;
                    break;
                case ImageTiffContentType:
                    imageFileExtension = ImageTiffFileExtension;
                    break;
                case ImagePngContentType:
                    imageFileExtension = ImagePngFileExtension;
                    break;
                default:
                    imageFileExtension = null;
                    break;
            }
            return imageFileExtension;
        }

        WpfPayload(Package p = null)
        {
            this._package = p;
        }

        private Package CreatePackage(Stream stream)
        {
            _package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite);
            return _package;
        }


        // Generates a image part Uri for the given image index
        private static string GetImageName(int imageIndex, string imageContentType)
        {
            string imageFileExtension = GetImageFileExtension(imageContentType);
            return XamlImageName + (imageIndex + 1) + imageFileExtension;
        }

        // Generates a relative URL for using from within xaml Image tag.
        private static string GetImageReference(string imageName)
        {
            return "." + imageName; // imageName is supposed to be created by GetImageName method
        }

        private PackagePart CreateWpfEntryPart()
        {
            // Define an entry part uri
            Uri entryPartUri = new Uri(XamlPayloadDirectory + XamlEntryName, UriKind.Relative);

            // Create the main xaml part
            PackagePart part = _package.CreatePart(entryPartUri, XamlContentType, CompressionOption.Normal);
            // Compression is turned off in this mode.
            //NotCompressed = -1,
            // Compression is optimized for a resonable compromise between size and performance. 
            //Normal = 0,
            // Compression is optimized for size. 
            //Maximum = 1,
            // Compression is optimized for performance. 
            //Fast = 2 ,
            // Compression is optimized for super performance. 
            //SuperFast = 3,

            // Create the relationship referring to the entry part
            PackageRelationship entryRelationship = _package.CreateRelationship(entryPartUri, TargetMode.Internal, XamlRelationshipFromPackageToEntryPart);

            return part;
        }

        private void CreateImagePart(PackagePart sourcePart, BitmapSource imageSource, string imageContentType, int imageIndex)
        {
            // Generate a new unique image part name
            string imagePartUriString = GetImageName(imageIndex, imageContentType);

            // Define an image part uri
            Uri imagePartUri = new Uri(XamlPayloadDirectory + imagePartUriString, UriKind.Relative);

            // Create a part for the image
            PackagePart imagePart = _package.CreatePart(imagePartUri, imageContentType, CompressionOption.NotCompressed);

            // Create the relationship referring from the enrty part to the image part
            PackageRelationship componentRelationship = sourcePart.CreateRelationship(imagePartUri, TargetMode.Internal, XamlRelationshipFromXamlPartToComponentPart);

            // Encode the image data
            BitmapEncoder bitmapEncoder = GetBitmapEncoder(imageContentType);
            bitmapEncoder.Frames.Add(BitmapFrame.Create(imageSource));

            // Save encoded image data into the image part in the package
            Stream imageStream = imagePart.GetStream();
            using (imageStream)
            {
                bitmapEncoder.Save(imageStream);
            }
        }


        internal PackagePart GetWpfEntryPart()
        {
            PackagePart wpfEntryPart = null;

            // Find a relationship to entry part
            PackageRelationshipCollection entryPartRelationships = _package.GetRelationshipsByType(XamlRelationshipFromPackageToEntryPart);
            PackageRelationship entryPartRelationship = null;
            foreach (PackageRelationship packageRelationship in entryPartRelationships)
            {
                entryPartRelationship = packageRelationship;
                break;
            }

            // Get a part referred by this relationship
            if (entryPartRelationship != null)
            {
                // Get entry part uri
                Uri entryPartUri = entryPartRelationship.TargetUri;

                // Get the enrty part
                wpfEntryPart = _package.GetPart(entryPartUri);
            }

            return wpfEntryPart;
        }



        [System.Security.SecurityCritical]
        internal static Stream SaveImage(BitmapSource bitmapSource, string imageContentType)
        {
            MemoryStream stream = new MemoryStream();
            // Create the wpf package in the stream
            WpfPayload wpfPayload = new WpfPayload();
            using (wpfPayload.CreatePackage(stream))
            {
                PackagePart xamlEntryPart = wpfPayload.CreateWpfEntryPart();

                Stream xamlPartStream = xamlEntryPart.GetStream();
                using (xamlPartStream)
                {
                    int imageIndex = 0;
                    string imageReference = GetImageReference(GetImageName(imageIndex, imageContentType));

                    StreamWriter xamlPartWriter = new StreamWriter(xamlPartStream);
                    using (xamlPartWriter)
                    {
                        string xamlText =
                            "<Span xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">" +
                            "<InlineUIContainer><Image " +
                            "Width=\"" +
                            bitmapSource.Width + "\" " +
                            "Height=\"" +
                            bitmapSource.Height + "\" " +
                            "><Image.Source><BitmapImage CacheOption=\"OnLoad\" UriSource=\"" +
                            imageReference +
                            "\"/></Image.Source></Image></InlineUIContainer></Span>";
                        xamlPartWriter.Write(xamlText);
                    }
                    wpfPayload.CreateImagePart(xamlEntryPart, bitmapSource, imageContentType, imageIndex);
                }
            }
            return stream;
        }

        static int _wpfPayloadCount; // used to disambiguate between all acts of loading from different WPF payloads.


        internal static object LoadElement(Stream stream)
        {
            Package package = Package.Open(stream, FileMode.Open, FileAccess.Read);
            WpfPayload wpfPayload = new WpfPayload(package);

            PackagePart xamlEntryPart = wpfPayload.GetWpfEntryPart();


            int newWpfPayoutCount = _wpfPayloadCount++;
            Uri payloadUri = new Uri("payload://wpf" + newWpfPayoutCount, UriKind.Absolute);
            Uri entryPartUri = PackUriHelper.Create(payloadUri, xamlEntryPart.Uri); // gives an absolute uri of the entry part
            Uri packageUri = PackUriHelper.GetPackageUri(entryPartUri); // extracts package uri from combined package+part uri
            PackageStore.AddPackage(packageUri, wpfPayload.Package); // Register the package
            ParserContext parserContext = new ParserContext();
            parserContext.BaseUri = entryPartUri;

            object xamlObject = XamlReader.Load(xamlEntryPart.GetStream(), parserContext);
            // Remove the temporary uri from the PackageStore
            PackageStore.RemovePackage(packageUri);

            return xamlObject;
        }

        public Package Package
        {
            get { return _package; }
        }

    };
}

Next if we have below RichTextBox:

<RichTextBox x:Name="RtbCompose" Width="500" Height="300">
            <FlowDocument x:Name="FdDocument">
                <Paragraph x:Name="Para1"></Paragraph>
            </FlowDocument>
</RichTextBox>        
<Button  Content="Paste image" HorizontalAlignment="Left" Margin="96,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="Button_Click"/>

On Button_Click we can save our image from the ClipBoard. First I got the Stream of package and next we can convert this as XamlReader.Load().

 private void Button_Click(object sender, RoutedEventArgs e)
    {
        BitmapSource image = Clipboard.GetImage();
        Stream packagedImage = WpfPayload.SaveImage(image, WpfPayload.ImageBmpContentType);
        object element = WpfPayload.LoadElement(packagedImage);
        Para1.Inlines.Add(element as Span);
    }

Result we can save with XAMLPackage.

   public byte[] SaveAllContent(RichTextBox rtb)
    {
        var content = new TextRange(rtb.Document.ContentStart, rtb_Main.Document.ContentEnd);
        using (MemoryStream ms = new MemoryStream())
        {
            content.Save(ms, DataFormats.XamlPackage, true);
            return ms.ToArray();
        }

    }

    public void LoadAllContent(byte [] bd, RichTextBox rtb)
    {
        var content = new TextRange(rtb.Document.ContentStart, rtb_Main.Document.ContentEnd);
        MemoryStream ms = new MemoryStream(bd);
        content.Load(ms, System.Windows.DataFormats.XamlPackage);
    }

With this solution Undo() and Redo() works fine :)

like image 29
arkadykl Avatar answered Sep 17 '25 00:09

arkadykl