在 XAML 中设置的 ObservableCollection<T> 类型的 DependencyProperty 包含的项数多于声明的项数。 C#

A DependencyProperty of Type ObservableCollection<T> set in XAML contains more items than there are declared. c#

我有一个 UWP 应用程序,我在其中创建了一个模板化控件 'Bracket',其中包含如下声明的 DependencyProperty:

public ObservableCollection<BaseControl> Content
{
   get { return (ObservableCollection<BaseControl>)GetValue(ContentProperty); }
   set
   {
       SetValue(ContentProperty, value);
       addChildren();
   }
}

public static readonly DependencyProperty ContentProperty =
    DependencyProperty.Register(nameof(Content), typeof(ObservableCollection<BaseControl>), 
        typeof(Bracket), new PropertyMetadata(new ObservableCollection<BaseControl>()));

在 XAML 中像这样使用控件时一切正常:

<Math:Bracket>
    <Math:Bracket.Content>
         <Math:TextBlock Text="4" />
         <Math:TextBlock Text="x" />
    </Math:Bracket.Content>
</Math:Bracket>

(注意:classes 'Bracket' 和 'TextBlock' 均派生自 'BaseControl')
但是,当我尝试在这样的支架中添加支架时:

<Math:Bracket>
    <Math:Bracket>
        <Math:Bracket.Content>
            <Math:TextBlock Text="4" />
            <Math:TextBlock Text="x" />
        </Math:Bracket.Content>
    </Math:Bracket>
</Math:Bracket>

我遇到异常:'Element is already the child of another element'
经过一些调试后,我发现,在两个 Bracket 对象中的每个对象中,属性 内容 包含 3 个元素 :TextBlocks 和内部 Bracket。
我期望和想要的是,外括号的内容仅包含内括号,而内括号的内容仅包含两个 TextBlock。
由于面板例如。 StackPanel 具有非常相似的功能(它包含子项,其中一个子项也可以是 StackPanel 类型)所以我查找了面板的代码 class。但是似乎使用了 IAddChild 接口。
在 msdn 文档 (https://msdn.microsoft.com/de-de/library/system.windows.markup.iaddchild(v=vs.110).aspx) 中,据说这是过时的并且 "Collection behavior is now integrally part of the XAML type system"。因此我使用了一个集合,但它产生了不同的结果。
我该如何解决这个问题?或者我应该使用不同的方法来实现此功能(也许通过使用 IAddChild 接口?

对于控件的每个实例,此 属性 的每个 实例的 默认值是 相同的 ObservableCollection :

public static readonly DependencyProperty ContentProperty =
    DependencyProperty.Register(nameof(Content), typeof(ObservableCollection<BaseControl>), 
        typeof(Bracket), new PropertyMetadata(new ObservableCollection<BaseControl>()));

对于每个实例,XAML parser/whatever 将其 children 添加到其 Content。但它始终是相同的集合!所以第一个得到它的 children,这导致第二个也得到那些 children。然后它添加第二个的 children,同样,它们都有对同一个集合的引用。

你不能给 PrpertyMetadata 一个引用类型的默认 non-null 值,除非它是像 System.String 这样不可变的东西。您可以共享相同的字符串值。

默认使用null,并在构造函数中使用新集合进行初始化,因此Bracket的每个实例都有自己的children集合。

public Bracket()
{
    Content = new ObservableCollection<BaseControl>();
}


public static readonly DependencyProperty ContentProperty =
    DependencyProperty.Register(nameof(Content), typeof(ObservableCollection<BaseControl>), 
        typeof(Bracket), new PropertyMetadata(null));

顺便说一句,setter 中的 addChildren(); 看起来很可疑,但 setter 可能永远不会被调用。 XAML 不会调用它。框架直接调用DependencyObject.SetValue()。放置一个断点并查看。除非您自己的代码想将常规 Content 属性 与 get 和 set 一起使用,否则这是惯例,但并非绝对必要。鉴于 属性 经常会被设置为 而不会触及 setter,但是,您永远不应该在其中放置任何额外的代码。 属性 值更改时应该发生的任何事情都应该在依赖性 属性 上的 PropertyChanged 处理程序中完成。