views:

22

answers:

1

I have a user control that has a grid (the one you get automatically when you create a user control) and a canvas within that.

<Grid x:Name="LayoutRoot" Background="White">
    <Canvas x:Name="SurfaceCanvas">
    </Canvas>
</Grid>

In the CS file, I've defined an "Items" collection.

public ObservableCollection<TestItem> Items {
    get { return (ObservableCollection<TestItem>)GetValue(ItemsProperty); }
    set { SetValue(ItemsProperty, value); }
}

public static readonly DependencyProperty ItemsProperty =
    DependencyProperty.Register("Items", typeof(ObservableCollection<TestItem>),
    typeof(TestControl),
    new PropertyMetadata(new ObservableCollection<TestItem>()));

The TestItem class declaration:

public class TestItem : ContentControl { ... }

Items are added to it via XAML.

<my:TestControl x:Name="ControlOne" Height="100" Width="100">
    <my:TestControl.Items>
        <my:TestItem x:Name="ItemOne">One</my:TestItem>
    </my:TestControl.Items>
</my:TestControl>

<my:TestControl x:Name="ControlTwo" Height="100" Width="100">
    <my:TestControl.Items>
        <my:TestItem x:Name="ItemTwo">Two</my:TestItem>
    </my:TestControl.Items>
</my:TestControl>

When items are added to the collection, I add them to the canvas.

void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
    switch(e.Action) {
        case NotifyCollectionChangedAction.Add:
            foreach(Control item in e.NewItems) {
                SurfaceCanvas.Children.Add(item);
            }
        break;
    }
}

Now, the problem.

If there is one instance of this control, all is well. But when I define a second instance, I receive an InvalidOperationException: "Element is already the child of another element" on the item added to ControlTwo.

When I step through and watch as elements are added to the canvas, what happens is that it creates ItemOne and adds it to ControlOne, then creates ItemTwo and adds it to ControlOne before trying to add it to ControlTwo. This results in an exception because you can't have an element parented to two controls at once.

My guess is that it has something to do with the fact that the canvas in each instance has the same name, so when it resolves "SurfaceCanvas", it gets two instances back and adds to each in order. This is just a guess based on observation.

What am I doing wrong?

+2  A: 

Took me a minute, but this is tricky:

You have a default value for your DependencyProperty. The default value is only created one time for your DependencyProperty and then assigned to both instances of your TestControl. This way, when you add something to TestControl.Items you add it to the common ObservbleCollection, which now has two CollectionChanged event delegates, each of which adds the new Items to their respective Canvas.

Remove the default value of ItemsProperty. Either create a ObservbleCollection in the constructor, or the xaml interpreter will create one for each TestControl.

Rule: Use a default value for DependencyProperty only with value types, not reference types.

Ozan
Thank you for furthering my education by one step.
oakskc
+1, This is a classic gotcha for all DependencyProperties that have a reference type. However I would relax the Rule a little to "Use default value only for value types __or__ for references to immutables". A good example of an "immutable" would be a `String`.
AnthonyWJones