简体   繁体   中英

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

I have a UWP-App in which I created a Templated Control 'Bracket' that contains a DependencyProperty declared like this:

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

When using the Control like this in XAML everything works fine:

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

(Note: Both the classes 'Bracket' and 'TextBlock' are derived from 'BaseControl')
However when I try to add a Bracket in a Bracket like this:

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

I get an Exception: 'Element is already the child of another element'
After some debugging i found out, that in each of the two Bracket objects the property Content contains 3 Elements : Both the TextBlocks and the inner Bracket.
What i expected and what i want is, that the outer Bracket's Content contains the inner Bracket only and the inner Bracket's Content to contain the two TextBlocks only.
Since a Panel eg. a StackPanel has a very similar function (it contains children where one children can also be of type StackPanel) so i looked up the code of the Panel class. However it seems that there the IAddChild interface is used.
In the msdn documentation ( https://msdn.microsoft.com/de-de/library/system.windows.markup.iaddchild(v=vs.110).aspx ) it is said that this is obsolete and that "Collection behavior is now integrally part of the XAML type system". Therefore I used a collection but it produces a different result.
How can i fix this issue? Or should i use a different approach to implement this functionality (maybe by using the IAddChild interface?

The default value of every instance of this property, for every instance of the control, is the same ObservableCollection :

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

For each instance, the XAML parser/whatever adds its children to its Content . But it's always the same collection! So the first one gets its children, which results in the second one getting those children too. Then it adds the second one's children, and again, both of them have a reference to the same collection.

You can't give PrpertyMetadata a default non-null value for a reference type, unless it's something immutable like System.String . You can share the same string value around.

Use null for the default, and initialize with a new collection in the constructor, so each instance of Bracket gets its own children collection.

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


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

By the way, addChildren(); in your setter looks very suspicious, but the setter probably never gets called. XAML won't call it. The framework calls DependencyObject.SetValue() directly. Put in a breakpoint and see. Unless your own code wants to use that conventional Content property with the get and set, it's customary, but not strictly necessary. Given that the property will very often be set without touching that setter , however, you should never, ever put any extra code in there. Anything that should happen when the property value changes should be done in a PropertyChanged handler on the dependency property.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM