Search code examples
c#wpfxamlbindingattached-properties

Why does DataContext fail to inherit on objects constructed as attached properties?


While constructing an object in xaml, the DataContext seems to resolve properly most of the time, but constructing that same object directly in the scope of an Attached Property seems to block DataContext inheritance.

In this, there are several definitions required for recreation. I'm happy to show the code, but for brevity's sake here is the outline:

  1. A view model, ViewModel, with a property, ViewModel.MyProperty of type string set to "123456789abc"
  2. A custom object, class FrameworkObject : FrameworkElement - no UI defined, but has a DataContext. This object has a defined dependency property, string FrameworkObject.MyDependencyProperty
  3. An attached property, AttachedProperty.FrameworkObject, which takes an object of type FrameworkObject
  4. A .Net Framework WPF Application where we will do the testing

Creating the object as an element in the Visual Tree successfully binds the value

    <!--
        System.Windows.Data Warning: 56 : Created BindingExpression (hash=52203868) for Binding (hash=27504314)
        System.Windows.Data Warning: 58 :   Path: 'MyProperty'
        System.Windows.Data Warning: 60 : BindingExpression (hash=52203868): Default mode resolved to OneWay
        System.Windows.Data Warning: 61 : BindingExpression (hash=52203868): Default update trigger resolved to PropertyChanged
        System.Windows.Data Warning: 62 : BindingExpression (hash=52203868): Attach to StackOverflowExamples.FrameworkObject.MyProperty (hash=34181910)
        System.Windows.Data Warning: 67 : BindingExpression (hash=52203868): Resolving source
        System.Windows.Data Warning: 70 : BindingExpression (hash=52203868): Found data context element: FrameworkObject (hash=34181910) (OK)
        System.Windows.Data Warning: 78 : BindingExpression (hash=52203868): Activate with root item ViewModel (hash=66824994)
        System.Windows.Data Warning: 108 : BindingExpression (hash=52203868):   At level 0 - for ViewModel.MyProperty found accessor RuntimePropertyInfo(MyProperty)
        System.Windows.Data Warning: 104 : BindingExpression (hash=52203868): Replace item at level 0 with ViewModel (hash=66824994), using accessor RuntimePropertyInfo(MyProperty)
        System.Windows.Data Warning: 101 : BindingExpression (hash=52203868): GetValue at level 0 from ViewModel (hash=66824994) using RuntimePropertyInfo(MyProperty): '123456789abc'
        System.Windows.Data Warning: 80 : BindingExpression (hash=52203868): TransferValue - got raw value '123456789abc'
        System.Windows.Data Warning: 89 : BindingExpression (hash=52203868): TransferValue - using final value '123456789abc'
    -->
    <local:FrameworkObject x:Name="CreatedInPanel" MyDependencyProperty="{Binding MyProperty, diag:PresentationTraceSources.TraceLevel=High}" />
    <TextBlock local:AttachedProperty.FrameworkObject="{Binding Path='', ElementName=CreatedInPanel}" Style="{StaticResource DisplayFromAttached}" />

Creating the object within the scope of the TextBlock fails to bind properly

        <!--
        System.Windows.Data Warning: 56 : Created BindingExpression (hash=53517805) for Binding (hash=3663598)
        System.Windows.Data Warning: 58 :   Path: 'MyProperty'
        System.Windows.Data Warning: 60 : BindingExpression (hash=53517805): Default mode resolved to OneWay
        System.Windows.Data Warning: 61 : BindingExpression (hash=53517805): Default update trigger resolved to PropertyChanged
        System.Windows.Data Warning: 62 : BindingExpression (hash=53517805): Attach to StackOverflowExamples.FrameworkObject.MyProperty (hash=51442863)
        System.Windows.Data Warning: 67 : BindingExpression (hash=53517805): Resolving source
        System.Windows.Data Warning: 70 : BindingExpression (hash=53517805): Found data context element: FrameworkObject (hash=51442863) (OK)
        System.Windows.Data Warning: 71 : BindingExpression (hash=53517805): DataContext is null
        System.Windows.Data Warning: 65 : BindingExpression (hash=53517805): Resolve source deferred
        System.Windows.Data Warning: 67 : BindingExpression (hash=53517805): Resolving source
        System.Windows.Data Warning: 70 : BindingExpression (hash=53517805): Found data context element: FrameworkObject (hash=51442863) (OK)
        System.Windows.Data Warning: 71 : BindingExpression (hash=53517805): DataContext is null
        System.Windows.Data Warning: 67 : BindingExpression (hash=53517805): Resolving source
        System.Windows.Data Warning: 70 : BindingExpression (hash=53517805): Found data context element: FrameworkObject (hash=51442863) (OK)
        System.Windows.Data Warning: 71 : BindingExpression (hash=53517805): DataContext is null
        System.Windows.Data Warning: 67 : BindingExpression (hash=53517805): Resolving source
        System.Windows.Data Warning: 70 : BindingExpression (hash=53517805): Found data context element: FrameworkObject (hash=51442863) (OK)
        System.Windows.Data Warning: 71 : BindingExpression (hash=53517805): DataContext is null
        System.Windows.Data Warning: 67 : BindingExpression (hash=53517805): Resolving source  (last chance)
        System.Windows.Data Warning: 70 : BindingExpression (hash=53517805): Found data context element: FrameworkObject (hash=51442863) (OK)
        System.Windows.Data Warning: 78 : BindingExpression (hash=53517805): Activate with root item <null>
        System.Windows.Data Warning: 106 : BindingExpression (hash=53517805):   Item at level 0 is null - no accessor
        System.Windows.Data Warning: 80 : BindingExpression (hash=53517805): TransferValue - got raw value {DependencyProperty.UnsetValue}
        System.Windows.Data Warning: 88 : BindingExpression (hash=53517805): TransferValue - using fallback/default value ''
        System.Windows.Data Warning: 89 : BindingExpression (hash=53517805): TransferValue - using final value ''
    -->
    <TextBlock Style="{StaticResource DisplayFromAttached}">
        <local:AttachedProperty.FrameworkObject>
            <local:FrameworkObject x:Name="CreatedInScope" MyDependencyProperty="{Binding MyProperty, diag:PresentationTraceSources.TraceLevel=High}" />
        </local:AttachedProperty.FrameworkObject>
    </TextBlock>

Using a data proxy will resolve the binding even when created in a control's scope

        <!--
        System.Windows.Data Warning: 56 : Created BindingExpression (hash=6968762) for Binding (hash=14964341)
        System.Windows.Data Warning: 58 :   Path: 'DataContext.MyProperty'
        System.Windows.Data Warning: 60 : BindingExpression (hash=6968762): Default mode resolved to OneWay
        System.Windows.Data Warning: 61 : BindingExpression (hash=6968762): Default update trigger resolved to PropertyChanged
        System.Windows.Data Warning: 62 : BindingExpression (hash=6968762): Attach to StackOverflowExamples.FrameworkObject.MyProperty (hash=47145209)
        System.Windows.Data Warning: 67 : BindingExpression (hash=6968762): Resolving source
        System.Windows.Data Warning: 70 : BindingExpression (hash=6968762): Found data context element: <null> (OK)
        System.Windows.Data Warning: 78 : BindingExpression (hash=6968762): Activate with root item FrameworkElement (hash=339559)
        System.Windows.Data Warning: 108 : BindingExpression (hash=6968762):   At level 0 - for FrameworkElement.DataContext found accessor DependencyProperty(DataContext)
        System.Windows.Data Warning: 104 : BindingExpression (hash=6968762): Replace item at level 0 with FrameworkElement (hash=339559), using accessor DependencyProperty(DataContext)
        System.Windows.Data Warning: 101 : BindingExpression (hash=6968762): GetValue at level 0 from FrameworkElement (hash=339559) using DependencyProperty(DataContext): <null>
        System.Windows.Data Warning: 106 : BindingExpression (hash=6968762):   Item at level 1 is null - no accessor
        System.Windows.Data Warning: 80 : BindingExpression (hash=6968762): TransferValue - got raw value {DependencyProperty.UnsetValue}
        System.Windows.Data Warning: 88 : BindingExpression (hash=6968762): TransferValue - using fallback/default value ''
        System.Windows.Data Warning: 89 : BindingExpression (hash=6968762): TransferValue - using final value ''
        System.Windows.Data Warning: 96 : BindingExpression (hash=6968762): Got PropertyChanged event from FrameworkElement (hash=339559) for DataContext
        System.Windows.Data Warning: 101 : BindingExpression (hash=6968762): GetValue at level 0 from FrameworkElement (hash=339559) using DependencyProperty(DataContext): ViewModel (hash=66824994)
        System.Windows.Data Warning: 108 : BindingExpression (hash=6968762):   At level 1 - for ViewModel.MyProperty found accessor RuntimePropertyInfo(MyProperty)
        System.Windows.Data Warning: 104 : BindingExpression (hash=6968762): Replace item at level 1 with ViewModel (hash=66824994), using accessor RuntimePropertyInfo(MyProperty)
        System.Windows.Data Warning: 101 : BindingExpression (hash=6968762): GetValue at level 1 from ViewModel (hash=66824994) using RuntimePropertyInfo(MyProperty): '123456789abc'
        System.Windows.Data Warning: 80 : BindingExpression (hash=6968762): TransferValue - got raw value '123456789abc'
        System.Windows.Data Warning: 89 : BindingExpression (hash=6968762): TransferValue - using final value '123456789abc'
    -->
    <ContentControl Content="{StaticResource DataProxy}" Visibility="Collapsed" />
    <TextBlock Style="{StaticResource DisplayFromAttached}">
        <local:AttachedProperty.FrameworkObject>
            <local:FrameworkObject x:Name="DataProxyBinding" MyDependencyProperty="{Binding DataContext.MyProperty, Source={StaticResource DataProxy}, diag:PresentationTraceSources.TraceLevel=High}" />
        </local:AttachedProperty.FrameworkObject>
    </TextBlock>

And the most confusing to me. Constructing the FrameworkObject within a ContentControl.Content seems to bind perfectly fine

        <!--
        System.Windows.Data Warning: 56 : Created BindingExpression (hash=63642613) for Binding (hash=38750844)
        System.Windows.Data Warning: 58 :   Path: 'MyProperty'
        System.Windows.Data Warning: 60 : BindingExpression (hash=63642613): Default mode resolved to OneWay
        System.Windows.Data Warning: 61 : BindingExpression (hash=63642613): Default update trigger resolved to PropertyChanged
        System.Windows.Data Warning: 62 : BindingExpression (hash=63642613): Attach to StackOverflowExamples.FrameworkObject.MyProperty (hash=16347077)
        System.Windows.Data Warning: 67 : BindingExpression (hash=63642613): Resolving source
        System.Windows.Data Warning: 70 : BindingExpression (hash=63642613): Found data context element: FrameworkObject (hash=16347077) (OK)
        System.Windows.Data Warning: 78 : BindingExpression (hash=63642613): Activate with root item ViewModel (hash=66824994)
        System.Windows.Data Warning: 108 : BindingExpression (hash=63642613):   At level 0 - for ViewModel.MyProperty found accessor RuntimePropertyInfo(MyProperty)
        System.Windows.Data Warning: 104 : BindingExpression (hash=63642613): Replace item at level 0 with ViewModel (hash=66824994), using accessor RuntimePropertyInfo(MyProperty)
        System.Windows.Data Warning: 101 : BindingExpression (hash=63642613): GetValue at level 0 from ViewModel (hash=66824994) using RuntimePropertyInfo(MyProperty): '123456789abc'
        System.Windows.Data Warning: 80 : BindingExpression (hash=63642613): TransferValue - got raw value '123456789abc'
        System.Windows.Data Warning: 89 : BindingExpression (hash=63642613): TransferValue - using final value '123456789abc'
    -->
    <ContentControl x:Name="ImplicitContent">
        <ContentControl.Template>
            <ControlTemplate TargetType="ContentControl">
                <TextBlock local:AttachedProperty.FrameworkObject="{TemplateBinding Content}" Style="{StaticResource DisplayFromAttached}" />
            </ControlTemplate>
        </ContentControl.Template>

        <local:FrameworkObject MyDependencyProperty="{Binding MyProperty, diag:PresentationTraceSources.TraceLevel=High}" />
    </ContentControl>

Through this, I've used a style, DisplayFromAttached, which is found in the enclosing panel's resource dictionary and is defined thus:

<Style x:Key="DisplayFromAttached" TargetType="TextBlock">
    <Setter Property="Text" Value="{Binding Path=(local:AttachedProperty.FrameworkObject).MyDependencyProperty, RelativeSource={RelativeSource Self}}" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=(local:AttachedProperty.FrameworkObject).MyDependencyProperty, RelativeSource={RelativeSource Self}}" Value="">
            <Setter Property="Text" Value="No value found" />
        </DataTrigger>
    </Style.Triggers>
</Style>

Why does the DataContext fail to inherit to an object constructed directly as an Attached Property?


Solution

  • Why would it?

    Let's remember that FrameworkElement.DataContext is implemented as a DependencyProperty with inheritance flag (see code source here, line 2704) and as such, the "inheritance" of the DataContext you are talking about takes place according to the documented inheritance rules:

    Property value inheritance enables child elements in a tree of elements to obtain the value of a particular property from parent elements.

    Property value inheritance is particularly about how property values can inherit from one element to another on the basis of the parent-child relationships within a tree of elements

    In your case, there is no such parent-child relationship between your TextBlock and its AttachedProperty.FrameworkObject value, not in the WPF sense. The fact that it is an attached or not dependency property doesn't have an impact actually.


    Some comments on your attempts

    Creating the object as an element in the Visual Tree successfully binds the value

    Yes, because then the object inherits the same DataContext as the TextBlock because they have the same parent in the element tree.

    Creating the object within the scope of the TextBlock fails to bind properly

    Yes, because the TextBlock is not a parent of the object in the element tree. The TextBlock just happens to hold a reference to that object (I'm hiding the complexity of attached properties here).

    Using a data proxy will resolve the binding even when created in a control's scope

    Yes, because the object is the child of a ContentControl itself a child of an element with the correct DataContext.

    And the most confusing to me. Constructing the FrameworkObject within a ContentControl.Content seems to bind perfectly fine.

    Same as above.


    If you really need your object to have a DataContext in your second scenario, you have to set it yourself because it won't be naturally inherited:

    <TextBlock x:Name="textBlock" Style="{StaticResource DisplayFromAttached}">
        <local:AttachedProperty.FrameworkObject>
            <local:FrameworkObject DataContext="{Binding ElementName=textBlock, Path=DataContext}" x:Name="CreatedInScope" MyDependencyProperty="{Binding MyProperty, diag:PresentationTraceSources.TraceLevel=High}" />
        </local:AttachedProperty.FrameworkObject>
    </TextBlock>