Search code examples
c#bindinguwpdependency-propertiesattached-properties

Collection-Type Attached Properties


I would like to extend FrameworkElement elements like the Button class by collection-type attached property that collect DependencyObjects.

The difficulty is that the bindings to the collection items do not work: No debug nor runtime error shows up but the bound source gets never called.

I noted that the collection-type attached property does not inherit from class DependencyObject.

I guess the DataContext property will be inherited by any child DependencyObject object (as long as the parent is also a DependencyObject object). As the collection-type attached property does not inherit from DependencyObject the DataContext property inheritance does not occur.

  1. I wonder why instances of DependencyObject can inherit the DataContext property as DataContext is a property defined in FrameworkElement? How does the DependencyObject manage the DataContext lookup?
  2. Why does specifying the binding source with ElementName=PageName not work as well (e.g. {Binding MyProperty="{Binding DataContext.PropertySource1, ElementName=PageName})? If the DependencyObject is also responsible for ElementName lookups, how does it do it?
  3. Is there a UWP collection that inherits DependencyObject? (In WPF there is FreezableCollection<T> class but I couldn't find a pendant in the UWP environment.)

The XAML markup below shows an example extension, where the Binding does not work.

<Button Name="Button">
    <ext:MyExtension.MyCollection>
        <ext:MyDependencyObject MyProperty="{Binding PropertySource1}"/>
        <ext:MyDependencyObject MyProperty="{Binding PropertySource1}"/> 
    </ext:MyExtension.MyCollection>
</Button>

If I do the following extension for non-collection-type attached properties the binding can be resolved correctly.

<Button Name="Button">
    <ext:MyExtension.MyProperty>
        <ext:MyDependencyObject MyProperty="{Binding PropertySource1}"/>
    </ext:MyExtension.MyProperty>
</Button>

The code below shows a sample collection-type attached property implementation. Consider the attached property class also contains a definition for a non-colleciton-type attached property (which works correctly with binding).

public class MyDependencyObject: DependencyObject
{
    public object MyProperty
    {
        get { return (object)GetValue(MyPropertyProperty ); }
        set { SetValue(MyPropertyProperty , value); }
    }
    public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.Register("MyProperty", typeof(object), typeof(MyProperty), null);
}

public class MyPropertyCollection : ObservableCollection<MyDependencyObject> { }

public static class MyExtension
{
    // Collection-type AttachedProperty with DependencyObject items

    public static MyPropertyCollection GetMyPropertyCollection(DependencyObject obj)
    {
        MyPropertyCollection collection = (MyPropertyCollection )obj.GetValue(MyCollectionProperty );
        if (collection == null)
        {
            collection = new MyPropertyCollection();

            collection.CollectionChanged +=
                (sender, e) =>
                {
                    //intiailization of elements possible
                };


            obj.SetValue(MappingsProperty, collection);
        }

        return collection;
    }

    public static void SetMyPropertyCollection(DependencyObject obj, MyPropertyCollection value)
    {
        obj.SetValue(MyCollectionProperty , value);
    }

    public static readonly DependencyProperty MyCollectionProperty =
        DependencyProperty.RegisterAttached("MyCollection", typeof(MyPropertyCollection), typeof(MyExtension), null);


    // DependencyObject-type AttachedProperty

    public static MyProperty GetMapping(DependencyObject obj)
    {
        return (MyProperty )obj.GetValue(MyPropertyProperty );
    }

    public static void SetMapping(DependencyObject obj, MyProperty value)
    {
        obj.SetValue(MyPropertyProperty , value);
    }

    public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.RegisterAttached("MyProperty", typeof(MyDependencyObject), typeof(MyExtension), null);

}

Solution

  • In WPF it's possible via using FreezableCollection<T> or via inheriting from the Freezable class, IList<T> and IList:

    1. Inherit it from DependencyObject because there are no way to access two required methods of the DependencyObject, but Freezable has a wrapper around them.
    2. Implement IList<T> and IList as you want.
    3. When adding a new element to the collection call OnFreezablePropertyChanged(null, newItem).
    4. When removing an element from it call OnFreezablePropertyChanged(item, null).

    The OnFreezablePropertyChanged method internally will call two methods of DependencyObject to provide or remove the inheritance context (source).

    But in UWP there is no Freezable and there are no such methods, so until Windows 10.0.10240.0 it isn't possible. But if you are targeting v10.0.10240.0 or later you should use DependencyObjectCollection which is made for behaviors.

    The purpose of the DependencyObjectCollection class is mainly to support the tooling and portability of behaviors. Behaviors are a technique for defining certain basic interactions of a UI element entirely in XAML, without requiring an event handler and code-behind.

    So in UWP and WPF the inheritance context can be provided to children only if they are logical/visual children or they are dependency property values. And because of that the Binding with set ElementName doesn't work when it is used in your case. The ElementName property is used to resolve an object at runtime when the binding is attached to the dependency object and not at compile time.

    UWP bindings work like WPF bindings so for example see ElementObjectRef.GetObject from the second platform.