0

I'm using a RichTextBox thant can contains images. I import them with an OpenFileDialog and put then into an InlineUIContainer.

It is displayed and the xaml code is as follows.

<Paragraph>
    <Image Source="file://C:/Path/Test.png" Stretch="Fill" Width="200" Height="100" />
</Paragraph>

I can save my xaml document in the database, close it and open it again without any problems, the image will still be displayed.

But if I ever move the image from within the document oc copy and paste it, my xaml code becomes as follow :

<BlockUIContainer>
    <Image Stretch="Fill" Width="200" Height="100">
        <Image.Source>
            <BitmapImage BaseUri="pack://payload:,,wpf3,/Xaml/Document.xaml" UriSource="./Image1.png" CacheOption="OnLoad" />
        </Image.Source>
    </Image>
</BlockUIContainer>

It is now contained within a BlockUIContainer, which I don't want for display purposes, but it's not my bigger problem, because I lost the ImageSource, thus not able to display them again.

Do you have any solutions to prevent this behavior ?

I tried to intercept the Copy and Paste event with DataObject.AddCopyingHandler et DataObject.AddPastingHandler, but couldn't manage to get the desired result.

I also found this question Saving source string of image in richtextbox after moving or pasting from clipboard with a similar problem but I don't understand what is meant by "saving it to the XAML Package".

Rikube
  • 41
  • 7

2 Answers2

1

According to the documentation the DataFormats.XamlPackage

Specifies the Extensible Application Markup Language (XAML) package data format.

In other words, this format gives possibility to save document as a packed xaml. Actually, this is archived document: binary file.

Therefore, instead of saving to a file you can get bytes array and save in to the database as binary data (varbinary) and when this is necessary load it to the RichTextBox back.


Below you can find some example that demonstrate how you can use this format to save/load a document from the RichTextBox to save and restore documents containing images.

The MainWindow.xaml:

<Window x:Class="WpfApp11.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"        
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="550" 
        x:Name="mainWindow" Topmost="True">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <RichTextBox Grid.Row="0" x:Name="rtb" AllowDrop="True" Padding="2" FontSize="18"
                     HorizontalAlignment="Left"
                     VerticalScrollBarVisibility="Auto"
                     HorizontalScrollBarVisibility="Auto" >         
        </RichTextBox>

        <Grid Grid.Row="1" Margin="3">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>            
            <Button Grid.Column="1" Click="Button_Save" Margin="2" Padding="3">Save</Button>
            <Button Grid.Column="2" Click="Button_Load" Margin="2" Padding="3">Load</Button>
        </Grid>        
    </Grid>
</Window>

The MainWindow.xaml.cs:

using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace WpfApp11
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Save(object sender, RoutedEventArgs e) { rtb.SaveRtf(); }

        private void Button_Load(object sender, RoutedEventArgs e) { rtb.LoadRtf(); }
    }

    public static class RichTextBoxExt
    {
        public static void SaveRtf(this RichTextBox rtb)
        {
            var dlg = new Microsoft.Win32.SaveFileDialog
            {
                DefaultExt = ".xpack",
                Filter = "XamlPackage Files (*.xpack)|*.xpack|All Files|*.*"
            };

            if (dlg.ShowDialog() == true)
            {
                try
                {
                    var range = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
                    using (var ms = new MemoryStream())
                    {
                        range.Save(ms, DataFormats.XamlPackage);
                        using (var file = new FileStream(dlg.FileName, FileMode.OpenOrCreate, FileAccess.Write))
                        {
                            ms.WriteTo(file);
                        }
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                }
            }
        }

        public static void LoadRtf(this RichTextBox rtb)
        {
            var dlg = new Microsoft.Win32.OpenFileDialog
            {
                DefaultExt = ".xpack",
                Filter = "XamlPackage Files (*.xpack)|*.xpack|All Files|*.*"
            };

            if (dlg.ShowDialog() == true)
            {
                var range = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
                using (var stream = new StreamReader(dlg.FileName))
                {
                    try
                    {
                        range.Load(stream.BaseStream, DataFormats.XamlPackage);
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.Message);
                    }
                }
            }
        }
    }
}
Jackdaw
  • 7,626
  • 5
  • 15
  • 33
  • 1
    This answer is working well, and seems better for the storage aspect so I'm gonna adapt my program to work with this solution. Thanks a lot for your time and example ! – Rikube Jul 27 '23 at 10:02
0

I found another solution that is closer to my original question, if someone ever needs it.

I add the Handlers for Copy and Paste (alors works when you move the Image into the RichTextBox)

<RichTextBox x:Name="Rtb" Loaded="Rtb_Loaded"/>
private void Rtb_Loaded(object sender, RoutedEventArgs e)
{
    DataObject.AddCopyingHandler(Rtb, OnCopy);
    DataObject.AddPastingHandler(Rtb, OnPaste);
}

When the user copies, we read all the blocks of the document and store all the images within the selection. FindImages code

private readonly List<Image> _copiedImages = new List<Image>();

private void OnCopy(object sender, DataObjectCopyingEventArgs e)
{
    int i = 0;
    _copiedImages.Clear();
    TextPointer lineStart = rtb.Selection.Start.GetLineStartPosition(i);

    while (lineStart != null)
    {
        Block actualBlock = lineStart.Paragraph;
        List<Image> images = FindImages(actualBlock).ToList();
        if (images != null)
        {
            foreach (Image img in images)
            {
                _copiedImages.Add(img);
            }
        }

        lineStart = lineStart.Paragraph == rtb.Selection.End.Paragraph ? 
            null : rtb.Selection.Start.GetLineStartPosition(i++);        
    }
}

The OnPaste event is triggered when the user paste its clipboard, but the content is not in the RichTextBox yet. I haven't found any way to change the content to keep track of the source, but here is the trick that helped me found the solution :

private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
    rtb.TextChanged += OnPastedExecuted;
}

Indeed, now you have an event after the content has been pasted. I feel so dumb to miss this for so long, but now I can easily change the content just after it has been pasted.

private void OnPastedExecuted(object sender, TextChangedEventArgs e)
{
    List<Image> documentImages = FindImages(rtb.Document).ToList();
    int pos = 0;

    foreach (Image img in documentImages)
    {
        // The copied Images dont have name
        if (string.IsNullOrEmpty(img.Name))
        {
            // Generate a random name to prevent any issue in Xaml
            string name = System.IO.Path.GetRandomFileName();
            img.Name = name.Substring(0, name.LastIndexOf("."));
            img.Source = _copiedImages[pos].Source;
            pos++;
        }
    }
    rtb.TextChanged -= OnPastedExecuted;
}

And there it is ! I can easily move my images in the document, copy many and keep the source.

Let me know if you find any problem in the code or see any improvement / optimization !

Rikube
  • 41
  • 7