InvalidOperationException when trying to remove image manually from RichTextBox in WPF

I am trying to create an application where I can enter text and images into a RichTextBox, serialize it and then deserialize it and load it back into the RichTextBox so I can change it later. When I load the images from the serialized xml file everything is displayed correctly, but when I try to remove the image from the RichTextBox manually by pressing backspace I get the following exception: Cannot serialize the non-public type "System.Windows.Media" .Imaging.BitmapFrameDecode ".

Here I am retrieving and saving data from the RichTextBox. It checks all the blocks, and if it finds an image, it just stores the placeholder string in the "List" text, so when it returns, it will know how to return the image to that location:

public void GetFindingsData(FlowDocument flowDoc, List<string> text, List<byte[]> bytes)
    {
        foreach (Block block in flowDoc.Blocks)
        {
            if (block.GetType() == typeof(Paragraph))
            {
                foreach (Run run in ((Paragraph)block).Inlines)
                {
                    text.Add(run.Text);
                }
            }

            else if (block.GetType() == typeof(BlockUIContainer) && ((BlockUIContainer)block).Child.GetType() == typeof(Image))
            {
                Image img = (Image)((BlockUIContainer)block).Child;
                bytes.Add(Storage.ImageToByteArray(img));
                text.Add("imageplaceholder_" + (bytes.Count - 1).ToString());
            }
        }
    }

      

And this is how I returned this data to the FlowDocument for display in the RichTextBox:

public FlowDocument createFlowDocument(List<string> runs, List<byte[]> bytes)
    {
        FlowDocument flowDoc = new FlowDocument();
        int counter = 0;
        foreach (string run in runs)
        {
            if (run == "imageplaceholder_" + counter.ToString())
            {
                flowDoc.Blocks.Add(new BlockUIContainer(Storage.ByteArrayToImage(bytes[counter])));
                counter++;
            }

            else
            {
                Paragraph par = new Paragraph();
                par.Inlines.Add(run);
                flowDoc.Blocks.Add(par);
            }
        }

        return flowDoc;
    }

      

If you need it, this is how I serialize the data from the RichTextBox. All my other data is serialized to xml, but that doesn't work for images, so I serialize it to a byte array first:

public static byte[] ImageToByteArray(Image image)
    {
        byte[] imageBuffer = null;

        if (image != null)
        {
            using (var stream = new MemoryStream())
            {
                var encoder = new PngBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(image.Source as BitmapSource));
                encoder.Save(stream);
                imageBuffer = stream.ToArray();
            }
        }

        return imageBuffer;
    }

      

Here's how I'm serializing and deserializing everything to / from the xml file (although I don't think that's the problem here):

public static void SaveData(StoredData data)
    {
            XmlSerializer serializer = new XmlSerializer(typeof(StoredData));
            using (FileStream stream = new FileStream(readerString, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                serializer.Serialize(stream, data);
            }
    }

public static StoredData LoadData()
    {
        try
        {
            StoredData storedData = new StoredData();
            using (FileStream stream = new FileStream(readerString, FileMode.Open))
            {
                XmlSerializer deserializer = new XmlSerializer(typeof(StoredData));
                storedData = (StoredData)deserializer.Deserialize(stream);
            }
            return storedData;

        }

        catch
        {
            StoredData newData = new StoredData();

            XmlSerializer serializer = new XmlSerializer(typeof (StoredData));
            using (FileStream stream = new FileStream(readerString, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                serializer.Serialize(stream, newData);
            }

            return newData;
        }
    }

      

And this is how I return an image from a byte array:

public static Image ByteArrayToImage(Byte[] imageBytes)
    {
        using (MemoryStream stream = new MemoryStream(imageBytes))
        {
            BitmapDecoder decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);

            BitmapFrame frame = decoder.Frames.First();

            frame.Freeze();
            Image newImage = new Image();
            newImage.Source = frame;
            return newImage;
        }
    }

      

Any help would be greatly appreciated.

+3


source to share


1 answer


When the user removes the image from RichTextBox

, it copies the removed items to the undo stream, serializing them to XAML. This can be seen from the stack trace:

   at System.Windows.Markup.Primitives.MarkupWriter.VerifyTypeIsSerializable(Type type)
   at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope)
   at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope)
   at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item)
   at System.Windows.Markup.Primitives.MarkupWriter.SaveAsXml(XmlWriter writer, MarkupObject item)
   at System.Windows.Markup.Primitives.MarkupWriter.SaveAsXml(XmlWriter writer, Object instance)
   at System.Windows.Markup.XamlWriter.Save(Object obj, TextWriter writer)
   at System.Windows.Markup.XamlWriter.Save(Object obj)
   at System.Windows.Documents.TextTreeDeleteContentUndoUnit.CopyObjectNode(TextTreeObjectNode objectNode, ContentContainer& container)
   at System.Windows.Documents.TextTreeDeleteContentUndoUnit.CopyContent(TextTreeNode node, TextTreeNode haltNode)
   at System.Windows.Documents.TextTreeDeleteContentUndoUnit..ctor(TextContainer tree, TextPointer start, TextPointer end)
   at System.Windows.Documents.TextTreeUndo.CreateDeleteContentUndoUnit(TextContainer tree, TextPointer start, TextPointer end)
   at System.Windows.Documents.TextContainer.DeleteContentInternal(TextPointer startPosition, TextPointer endPosition)

      

This causes a problem for you, because the type BitmapFrame

is abstract, but the type of actual return BitmapDecoder.Frames.First()

, BitmapFrameDecode

is internal, and therefore can not be serialized the XAML .

However, I don't understand why you need to use BitmapDecoder

. Why not just use a regular one BitmapImage

? If I load your image using the following code, the inserted image can now be removed:

    public static Image ByteArrayToImage(Byte[] imageBytes)
    {
        var stream = new MemoryStream(imageBytes);
        {
            var frame = new BitmapImage();
            frame.BeginInit();
            frame.CacheOption = BitmapCacheOption.OnLoad;
            frame.StreamSource = stream;
            frame.EndInit();
            frame.Freeze();
            Image newImage = new Image() { Source = frame };
            return newImage;
        }
    }

      



Well, if I did that, I encountered a secondary problem: if I tried to undo the deletion of the image after it was deleted, it won't return. Junk BlockUIContainer

with a blank image will be restored instead . I haven't been able to pinpoint exactly why this happened, but it has something to do with being BitmapImage.BaseUri

empty when it BitmapImage

was created from a memory stream, not from UriSource

.

I managed to get around this, in turn, by serializing for the temporary XamlPackage

:

    public static void AddBlockUIContainerImage(this FlowDocument doc, byte[] imageBytes)
    {
        var image2 = Storage.ByteArrayToImage(imageBytes);
        using (var stream = new MemoryStream())
        {
            var subDoc = new FlowDocument();
            subDoc.Blocks.Add(new BlockUIContainer(image2));
            new TextRange(subDoc.ContentStart, subDoc.ContentEnd).Save(stream, DataFormats.XamlPackage, true);
            stream.Seek(0, SeekOrigin.Begin);
            var target = new TextRange(doc.ContentEnd, doc.ContentEnd);
            target.Load(stream, DataFormats.XamlPackage);
        }
    }

      

Having done this, it is BaseUri

no longer null; undo, redo, delete, undo delete and redo deletion of all worked out.

Since this workaround means you are effectively deserializing the image twice, you might want to store each image as an XamlPackage

encoded byte array rather than an- encoded byte array Png

.

0


source







All Articles