Привязка к вложенному элементу в присоединенном свойстве в WPF - PullRequest
0 голосов
/ 26 марта 2020

Я пытаюсь привязать элемент, вложенный в присоединенное свойство, к моему DataContext, но проблема в том, что присоединенное свойство не является частью логического дерева и, следовательно, неправильно устанавливает или привязывает к контексту данных родительский объект. Свойство зависимости, в данном случае Value, всегда равно нулю.

Вот несколько примеров 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>

Из-за реализации это не может быть одно присоединенное свойство - ему нужно быть коллекцией, которая допускает n сущностей. Другим приемлемым решением будет размещение идентификаторов непосредственно в узле, но я не думаю, что этот синтаксис возможен без явного включения этих элементов в логическое дерево. то есть ..

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

Вот начало реализации 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)));
}

Я попытался сделать базовый класс Identifiers реализовать Freezable в надеется, что это было бы для наследования данных и связывания контекста, но это не имело никакого эффекта (вероятно, потому что он вложен в другой слой - свойство присоединенного).

Еще пара ключевых моментов:

  • Я бы хотел, чтобы это работало на любых UIElement, а не только StackPanel s
  • . Identifier s не являются частью визуального дерева. Они не имеют и не должны иметь визуальных элементов
  • , так как это внутренняя библиотека, я бы предпочел не требовать Source или RelativeSource для привязки, так как это не интуитивно понятно, что это необходимо сделать

Возможно ли связать с унаследованным DataContext в этом слое разметки? Нужно ли вручную добавлять их в логическое дерево? Если да, то как?

Спасибо!

1 Ответ

1 голос
/ 27 марта 2020

В дополнение к тому, что Identifier наследуется от Freezable, вам также потребуется использовать FreezableCollection вместо Collection<Identifier> в качестве присоединенного типа свойства. Это гарантирует, что цепочка наследования не разорвана.

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

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

Создайте пользовательскую коллекцию:

public class IdentifierCollection : FreezableCollection<Identifier> { }

И измените вложенное свойство, чтобы использовать эту коллекцию:

[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;
    }
}

Пример использования:

<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>

Sample demo

...