当我尝试从 WPF 中的 RichTextBox 手动删除图像时出现 InvalidOperationException

InvalidOperationException when I try to delete an image manually from RichTextBox in WPF

我正在尝试创建一个应用程序,我可以在其中将文本和图像输入到 RichTextBox 中,将其序列化,然后将其反序列化并将其加载回 RichTextBox,以便我以后可以更改它。当我从序列化的 xml 文件加载图像时,一切都正确显示,但是当我尝试通过按退格键手动从 RichTextBox 中删除图像时,出现以下异常:无法序列化非 public 输入 'System.Windows.Media.Imaging.BitmapFrameDecode'.

以下是我从 RichTextBox 中提取和存储数据的方法。它会检查所有的块,如果它找到一个图像,那么它只是在列表文本中保存一个占位符字符串,这样当它返回时它就会知道将图像放回那个位置:

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());
            }
        }
    }

下面是我如何将该数据放回 FlowDocument 以在 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;
    }

如果您需要,下面是我如何序列化 RichTextBox 中的数据。我的所有其他数据都序列化为 xml,但这不适用于图像,所以我首先将其序列化为字节数组:

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

这是我序列化和反序列化所有内容的地方 to/from 一个 xml 文件(尽管我认为问题不在这里):

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

下面是我如何从字节数组取回图像:

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

如有任何帮助,我们将不胜感激。

当用户从 RichTextBox 中删除图像时,它会 copies 通过将已删除的项目序列化为 XAML 来撤消流。从堆栈跟踪可以看出这一点:

   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)

这会给您带来麻烦,因为类型 BitmapFrame is abstract, and the type actually returned by BitmapDecoder.Frames.First(), BitmapFrameDecode, is internal, and thus can't be serialized to XAML.

但是,我不明白你为什么需要使用 BitmapDecoder。为什么不直接使用常规 BitmapImage?如果我使用以下代码加载您的图像,现在可以删除插入的图像:

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

嗯,做完这件事,我发现了一个次要问题:如果我在删除图片后尝试撤消删除,它就不会回来了。相反,将恢复带有空图像的垃圾 BlockUIContainer。我无法确定发生这种情况的确切原因,但这与 BitmapImage.BaseUri was null when the BitmapImage was created from a memory stream rather than from a UriSource.

这一事实有关

反过来,我可以通过序列化为临时文件来解决这个问题 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);
        }
    }

完成此操作后,BaseUri 不再为空;撤消、重做、删除、撤消删除和重做删除全部有效。

由于此解决方法意味着您实际上将图像反序列化两次,因此您可能需要考虑将每个图像存储为 XamlPackage 编码字节数组而不是 Png 编码字节数组。