在 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
处理程序中完成。
我有一个 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
处理程序中完成。