Jakub Krampl
Jakub Krampl

Reputation: 1794

Custom RichTextBlock raise exception "Element is already the child of another element"

I use FlipView control and inside ItemTemplate I have custom RichTextBlock with Elements property created like collection of Run elements. It works, however, sometimes I am receiving exception "WinRT information: Element is already the child of another element." And I really don't understand why.

Could somebody please explain me why it is happening?

Sample - http://1drv.ms/1yZM8a5

Model (contains test data)

public class Page
{
    public Page()
    {
        Data = new List<Run>();
        Data.Add(new Run { Text = "1" });
        Data.Add(new Run { Text = "2" });
        Data.Add(new Run { Text = "3" });
        Data.Add(new Run { Text = "4" });
        Data.Add(new Run { Text = "5" });
    }
    public IList<Run> Data { get; set; }
}

ViewModel (collection of pages for FlipView)

public ObservableCollection<Page> Pages { get; set; }

// test data
Pages = new ObservableCollection<Page>();
Pages.Add(new Page()); // etc

XAML

<FlipView ItemsSource="{Binding Pages}">
    <FlipView.ItemTemplate>
        <DataTemplate>
            <local:ExtendedRichTextBlock Elements="{Binding Data}" />
        </DataTemplate>
    </FlipView.ItemTemplate>
</FlipView>

ExtendedRichTextBlock

Elements is dependency property and changes are caught here:

private static void OnElementsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ExtendedRichTextBlock sender = d as ExtendedRichTextBlock;

    if (sender != null)
    {
        sender.Content.Blocks.Clear();

        var x = e.NewValue as IList<Run>;
                
        if (x != null)
        {
            foreach (var item in x)
            {
                var p = new Paragraph();
                p.Inlines.Add(item);    // WinRT information: Element is already the child of another element.

                sender.Content.Blocks.Add(p);
            }
        }
    }
}

Conclusion

Just short conclusion from my discussion with Romasz in this chat.

Every time the event is fired you are clearing content and then populating it reusing Run elements. In the behind it may be quite complex - maybe some animations, transitions and so on, I'm also not sure if this is not made asynchronously - if so there may be situation when elements still have a parent and you try to reuse it. Consider better thing, populating every time with UI elements it's not a good idea - make your datatemlate with those UI elements and bind their content to some kind of collection.

So all elements must be created on the fly in OnElementsChanged.

Upvotes: 1

Views: 186

Answers (1)

Romasz
Romasz

Reputation: 29792

It seems that your program sometimes tries to make Run element a child of another control, while it already has a parent and UI elemnts can have only one parent.

In this case consider little change in your app:

private static void OnElementsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ExtendedRichTextBlock sender = d as ExtendedRichTextBlock;

    if (sender != null)
    {
        sender.Content.Blocks.Clear();

        var x = e.NewValue as IList<string>;

        if (x != null)
        {
            foreach (var item in x)
            {
                var p = new Paragraph();
                p.Inlines.Add(new Run { Text = item });    // WinRT information: Element is already the child of another element.
                sender.Content.Blocks.Add(p);
            }
        }
    }
}

Of course to make this work you need to change all IList<Run> to IList<string>.

This would ensure that every Run element is a new one without parent.

Upvotes: 1

Related Questions