简体   繁体   中英

Binding to Nested Element in Attached Property in WPF

I am trying to bind an element nested inside of an attached property to my DataContext , but the problem is that the attached property is not part of the logical tree and therefore does not properly set or bind to the data context of the parent object. The dependency property, in this case Value , is always null.

Here is some example XAML

<StackPanel>
    <!-- attached property of static class DataManager -->
    <local:DataManager.Identifiers>
        <local:TextIdentifier Value="{Binding Path=MyViewModelString}" />
        <local:NumericIdentifier Value="{Binding Path=MyViewModelInt}" />      
        <local:NumericIdentifier Value="{Binding Path=SomeOtherInt}" />
    </local:DataIdentifiers>
    <!-- normal StackPanel items -->
    <Button />
    <Button />
</StackPanel>

Due to the implementation, this cannot be a single attached property - it needs to be a collection that allows for n entities. Another acceptable solution would be to put the identifiers directly in the node, but I don't think this syntax is possible without including these element explicitly in the logical tree. ie..

<Button>
    <local:NumericIdentifier Value="{Binding}" />
    <local:TextIdentifier Value="{Binding}" />
    <TextBlock>Actual button content</TextBlock>
</Button>

Here is the start of the implementation of DataManager .

[ContentProperty("IdentifiersProperty")]
public static class DataManager
{
    public static Collection<Identifier> GetIdentifiers(DependencyObject obj)
    {
        return (Collection<Identifier>)obj.GetValue(IdentifiersProperty);
    }

    public static void SetIdentifiers(DependencyObject obj, Collection<Identifier> value)
    {
        obj.SetValue(IdentifiersProperty, value);
    }

    public static readonly DependencyProperty IdentifiersProperty =
        DependencyProperty.RegisterAttached("Identifiers", typeof(Collection<Identifier>), typeof(DataManager), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIdentifiersChanged)));
}

I've tried making the base class Identifiers implement Freezable in the hopes that it would for the inheritance of the data and binding context, but that did not have any effect (likely because it is nested inside another layer - the attached property).

A couple more key points:

  • I would like this to work on any UIElement , not just StackPanel s
  • The Identifier s are not part of the visual tree. They do not and should not have visual elements
  • as this is an internal library, I would prefer to avoid requiring a Source or RelativeSource to the binding as it is not intuitive that this needs to be done

Is it possible to bind to the inherited DataContext in this layer of the markup? Do I need to manually add these to the logical tree? If so, how?

Thanks!

In addition to having Identifier inherit from Freezable , you will need to also use FreezableCollection instead of Collection<Identifier> as attached property type. This will ensure that inheritance chain is not broken.

public class Identifier : Freezable
{
    ... // dependency properties 

    protected override Freezable CreateInstanceCore()
    {
        return new Identifier();
    }
}

Create a custom collection:

public class IdentifierCollection : FreezableCollection<Identifier> { }

And, modify attached property to use this collection:

[ContentProperty("IdentifiersProperty")]
public static class DataManager
{
    public static readonly DependencyProperty IdentifiersProperty =
                    DependencyProperty.RegisterAttached(
                            "Identifiers",
                            typeof(IdentifierCollection),
                            typeof(DataManager),
                            new FrameworkPropertyMetadata(OnIdentifiersChanged));

    ...

    public static void SetIdentifiers(UIElement element, IdentifierCollection value)
    {
        element.SetValue(IdentifiersProperty, value);
    }
    public static IdentifierCollection GetIdentifiers(UIElement element)
    {
        return element.GetValue(IdentifiersProperty) as IdentifierCollection;
    }
}

Sample usage:

<Window.DataContext>
    <local:TestViewModel 
        MyViewModelInt="123"
        MyViewModelString="Test string"
        SomeOtherInt="345" />
</Window.DataContext>

<StackPanel x:Name="ParentPanel" ... >
    <!-- attached property of static class DataManager -->
    <local:DataManager.Identifiers>
        <local:IdentifierCollection>
            <local:TextIdentifier Value="{Binding Path=MyViewModelString}" />
            <local:NumericIdentifier Value="{Binding Path=MyViewModelInt}" />
            <local:NumericIdentifier Value="{Binding Path=SomeOtherInt}" />
        </local:IdentifierCollection>
    </local:DataManager.Identifiers>
    <!-- normal StackPanel items -->

    <TextBlock Text="{Binding Path=(local:DataManager.Identifiers)[0].Value, 
        ElementName=ParentPanel, StringFormat=Identifer [0]: {0}}" />
    <TextBlock Text="{Binding Path=(local:DataManager.Identifiers)[1].Value, 
        ElementName=ParentPanel, StringFormat=Identifer [1]: {0}}" />
    <TextBlock Text="{Binding Path=(local:DataManager.Identifiers)[2].Value, 
        ElementName=ParentPanel, StringFormat=Identifer [2]: {0}}" />

</StackPanel>

示例演示

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