【问题标题】:Binding to Nested Element in Attached Property in WPF绑定到 WPF 附加属性中的嵌套元素
【发布时间】:2020-07-07 12:46:50
【问题描述】:

我正在尝试将嵌套在附加属性中的元素绑定到我的DataContext,但问题是附加属性不是逻辑树的一部分,因此没有正确设置或绑定到父对象。依赖属性,在本例中为 Value,始终为 null。

这是一些 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,而不仅仅是StackPanels
  • Identifiers 不是可视化树的一部分。它们没有也不应该有视觉元素
  • 因为这是一个内部库,我宁愿避免要求绑定 SourceRelativeSource,因为这样做并不直观

是否可以在标记的这一层绑定到继承的DataContext?我需要手动将这些添加到逻辑树吗?如果有,怎么做?

谢谢!

【问题讨论】:

  • 您是否考虑过为 DataManager.Identifiers 属性使用 MultiBinding?它将使用返回Collection&lt;Identifier&gt; 的IMultiValueConverter,这也将解决您如何使用集合实例初始化Identifiers 属性的问题。
  • 这可能仅通过使用单个附加属性来工作,但它也增加了对混合、转换器甚至更多标记的另一个要求。我可以试一试,但希望有一个更清洁的解决方案。

标签: c# wpf xaml data-binding attached-properties


【解决方案1】:

除了让Identifier 继承自Freezable 之外,您还需要使用FreezableCollection 而不是Collection&lt;Identifier&gt; 作为附加属性类型。这将确保继承链不会中断。

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>

【讨论】:

  • FreezableCollection 是我丢失的链接。这非常有效 - 谢谢!
猜你喜欢
  • 1970-01-01
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-27
  • 2018-05-14
  • 2015-08-30
  • 1970-01-01
相关资源
最近更新 更多