title: Insert contents from the one FlowDocument into another when using XamlReader and XamlWriter
body: <p>I use FlowDocument with BlockUIContainer and InlineUIContainer elements containing (or as base classes) some custom blocks - SVG, math formulas etc.
  Because of that using Selection.Load(stream, DataFormats.XamlPackage) wont work as the serialization will drop the contents of *UIContainers except if the Child property is an image as available in Microsoft reference source:</p>
  
  <pre><code>private static void WriteStartXamlElement(...)
  {
      ...
      if ((inlineUIContainer == null || !(inlineUIContainer.Child is Image)) &amp;&amp;
                  (blockUIContainer == null || !(blockUIContainer.Child is Image)))
      {
          ...
          elementTypeStandardized = TextSchema.GetStandardElementType(elementType, /*reduceElement:*/true);
      }
      ...
  }
  </code></pre>
  
  <p>The only option in this case is to use is to use XamlWriter.Save and XamlReader.Load which are working flawlessly, serialize and deserialize all required properties and objects of a FlowDocument yet the Copy+Paste must be implemented manually as default implementation of Copy+Paste uses Selection.Load/Save.</p>
  
  <p>Copy/Paste is critical as it is also used to handle dragging of elements in or between RichTextBox controls - the only way objects can be manipulated without custom dragging code.</p>
  
  <p>This is why I am looking to implement copy/paste using a FlowDocument serialization but unfortunately there are some issues with it:</p>
  
  <ol>
  <li>In current solution a whole FlowDocument object needs to be serialized/deserialized. Performance-wise it should not be a problem but I need to store information what selection range needs to be pasted from it (CustomRichTextBoxTag class).</li>
  <li><p>Apparently objects cannot be removed from one document and added to another (a dead-end I discovered recently): 'InlineCollection' element cannot be inserted in a tree because it is already a child of a tree.</p>
  
  <pre><code>[TextElementCollection.cs]
  public void InsertAfter(TextElementType previousSibling, TextElementType newItem)
  {
      ...
      if (previousSibling.Parent != this.Parent)
          throw new InvalidOperationException(System.Windows.SR.Get("TextElementCollection_PreviousSiblingDoesNotBelongToThisCollection", new object[1]
          {
              (object) previousSibling.GetType().Name
          }));
      ...
  }
  </code></pre>
  
  <p>I think about setting FrameworkContentElement._parent using reflection in all elements which need  to be moved to another document but that's a last resort hackish and dirty solution:</p></li>
  <li><p>In theory I can copy only required objects: (optional) partial run with text at the beginning of selection, all paragraphs and inlines in between and and (possibly) partial run at the end, encapsulate these in a custom class and serialize/deserialize using XamlReader/XamlWriter.</p></li>
  <li>Another solution I didn't think about.</li>
  </ol>
  
  <p>Here is the custom RichTextBox control implementation with partially working custom Copy/Paste code:</p>
  
  <pre><code>using System.IO;
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Documents;
  using System.Windows.Markup;
  
  namespace FlowMathTest
  {
      public class CustomRichTextBoxTag: DependencyObject
      {
          public static readonly DependencyProperty SelectionStartProperty = DependencyProperty.Register(
              "SelectionStart",
              typeof(int),
              typeof(CustomRichTextBoxTag));
  
          public int SelectionStart
          {
              get { return (int)GetValue(SelectionStartProperty); }
              set { SetValue(SelectionStartProperty, value); }
          }
  
          public static readonly DependencyProperty SelectionEndProperty = DependencyProperty.Register(
              "SelectionEnd",
              typeof(int),
              typeof(CustomRichTextBoxTag));
  
          public int SelectionEnd
          {
              get { return (int)GetValue(SelectionEndProperty); }
              set { SetValue(SelectionEndProperty, value); }
          }
      }
  
      public class CustomRichTextBox: RichTextBox
      {
          public CustomRichTextBox()
          {
              DataObject.AddCopyingHandler(this, OnCopy);
              DataObject.AddPastingHandler(this, OnPaste);
          }
  
          protected override void OnSelectionChanged(RoutedEventArgs e)
          {
              base.OnSelectionChanged(e);
              var tag = Document.Tag as CustomRichTextBoxTag;
              if(tag == null)
              {
                  tag = new CustomRichTextBoxTag();
                  Document.Tag = tag;
              }
              tag.SelectionStart = Document.ContentStart.GetOffsetToPosition(Selection.Start);
              tag.SelectionEnd = Document.ContentStart.GetOffsetToPosition(Selection.End);
          }
  
          private void OnCopy(object sender, DataObjectCopyingEventArgs e)
          {
              if(e.DataObject != null)
              {
                  e.Handled = true;
                  var ms = new MemoryStream();
                  XamlWriter.Save(Document, ms);
                  e.DataObject.SetData(DataFormats.Xaml, ms);
              }
          }
  
          private void OnPaste(object sender, DataObjectPastingEventArgs e)
          {
              var xamlData = e.DataObject.GetData(DataFormats.Xaml) as MemoryStream;
              if(xamlData != null)
              {
                  xamlData.Position = 0;
                  var fd = XamlReader.Load(xamlData) as FlowDocument;
                  if(fd != null)
                  {
                      var tag = fd.Tag as CustomRichTextBoxTag;
                      if(tag != null)
                      {
                          InsertAt(Document, Selection.Start, Selection.End, fd, fd.ContentStart.GetPositionAtOffset(tag.SelectionStart), fd.ContentStart.GetPositionAtOffset(tag.SelectionEnd));
                          e.Handled = true;
                      }
                  }
              }
          }
  
          public static void InsertAt(FlowDocument destDocument, TextPointer destStart, TextPointer destEnd, FlowDocument sourceDocument, TextPointer sourceStart, TextPointer sourceEnd)
          {
              var destRange = new TextRange(destStart, destEnd);
              destRange.Text = string.Empty;
  
              // insert partial text of the first run in the selection
              if(sourceStart.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
              {
                  var sourceRange = new TextRange(sourceStart, sourceStart.GetNextContextPosition(LogicalDirection.Forward));
                  destStart.InsertTextInRun(sourceRange.Text);
                  sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
                  destStart = destStart.GetNextContextPosition(LogicalDirection.Forward);
              }
  
              var field = typeof(FrameworkContentElement).GetField("_parent", BindingFlags.NonPublic | BindingFlags.Instance);
              while(sourceStart != null &amp;&amp; sourceStart.CompareTo(sourceEnd) &lt;= 0 &amp;&amp; sourceStart.Paragraph != null)
              {
                  var sourceInline = sourceStart.Parent as Inline;
                  if(sourceInline != null)
                  {
                      sourceStart.Paragraph.Inlines.Remove(sourceInline);
                      if(destStart.Parent is Inline)
                      {
                          field.SetValue(sourceInline, null);
                          destStart.Paragraph.Inlines.InsertAfter(destStart.Parent as Inline, sourceInline);
                      }
                      else
                      {
                          var p = new Paragraph();
                          destDocument.Blocks.InsertAfter(destStart.Paragraph, p);
                          p.Inlines.Add(sourceInline);
                      }
                      sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
                  }
                  else
                  {
                      var sourceBlock = sourceStart.Parent as Block;
                      field.SetValue(sourceBlock, null);
                      destDocument.Blocks.InsertAfter(destStart.Paragraph, sourceBlock);
                      sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p>And the question - is there an existing solution for custom Copy+Paste code for FlowDocument using XamlReader and XamlWriter?
  How to <strike>fix the code above so it won't complain about different FlowDocument object or</strike> work around this limitation?</p>
  
  <p>EDIT: As an experiment I implemented 2) so that objects can be moved from one FlowDocument to another. The code above is updated - all references to the "field" variable.</p>