自定义 RichTextBlock 引发异常 "Element is already the child of another element"

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

我使用 FlipView 控件,在 ItemTemplate 内部我有自定义 RichTextBlock,其中 Elements 属性 创建为 Run 元素的集合。它有效,但是,有时我会收到异常 “WinRT 信息:元素已经是另一个元素的子元素。” 我真的不明白为什么。

有人可以解释一下为什么会这样吗?

示例 - http://1drv.ms/1yZM8a5

模型(包含测试数据)

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(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 是依赖项 属性 并在此处捕获更改:

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

结论

我在 chat.

中与 Romasz 讨论的简短结论

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.

因此必须在 OnElementsChanged 中动态创建所有元素。

似乎您的程序有时会尝试使 运行 元素成为另一个控件的 child,而它已经具有 parent UI 个元素只能有一个 parent。

在这种情况下,请考虑对您的应用进行少量更改:

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

当然要完成这项工作,您需要将所有 IList<Run> 更改为 IList<string>

这将确保每个 运行 元素都是没有 parent.

的新元素