【问题标题】:XAML Binding to parent of data objectXAML 绑定到数据对象的父级
【发布时间】:2013-05-03 05:11:59
【问题描述】:

我定义了一个网格列。父网格从 ItemClass 类型的 ObservableCollection 中获取其项目。 ItemClass 有两个属性:String Foo 和 bool IsEditAllowed。

此列绑定到属性 Foo。有一个用于编辑单元格的控制模板。我想将 ItemClass.IsEditAllowed 属性绑定到模板中 TextBox 的 IsEnabled 属性。

问题是如何绑定它。这可以做到吗?下面的 XAML 在调试跟踪中让我“找不到与引用绑定的源”。

网格将允许我通过一些“自定义”事件将 ItemClass 本身绑定到字段,然后我可以绑定到它的任何属性。这很好,但看起来很笨拙。但如果这是唯一的方法,那就是唯一的方法。

<dxg:GridColumn
                 Header="Foo Column"
                 FieldName="Foo">
    <dxg:GridColumn.EditTemplate>
        <ControlTemplate>
            <TextBox Text="{Binding Value, Mode=TwoWay}"
                     IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}}" />
        </ControlTemplate>
    </dxg:GridColumn.EditTemplate>
</dxg:GridColumn>

【问题讨论】:

  • 您应该发布您的答案作为答案,并且不要使用俗语。

标签: wpf xaml data-binding binding


【解决方案1】:

有两种可能更简单的方法来设置此绑定。

1) 命名网格。然后您的绑定可能看起来像这样(假设 dxg:GridControl 有一个名为“Items”的属性,并且您已将 ItemClass 的实例分配给该属性):

<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, ElementName=MyGridControl />

2) 使用相对绑定,但查找 GridControl 而不是 GridControl 工作方式名义上内部的东西(即 GridControlContentPresenter)。这让您远离了 GridControl 的实现细节,与 GridControl 本身的属性相比,这些实现细节更有可能以破坏您的应用程序的方式发生变化。

<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, RelativeSource={RelativeSource AncestorType={x:Type dxg:GridControl}}}" />

您可能还想阅读 WPF/xaml 中的 the Visual Tree and the Logical Tree。相对绑定中的“祖先”指的是可视化树中的祖先,即父容器之类的东西,而不是超类或基类(我认为你已经发现)。

【讨论】:

  • 网格的 Items 属性绑定到多个 ItemsClass 实例的 ObservableCollection。 ItemsClass 的一个实例显示在网格的每一行中。所以在单元格模板中,我必须绑定到 Items[rowindex].IsEditAllowed,但是我从哪里获取 rowindex?
  • 抱歉,起初我误解了 ItemsClass 包含 ObservableCollection。您拥有的另一个潜力是 DataContext。大多数/所有 ItemControls 将项目容器上的 DataContext 设置为项目绑定到源集合的任何内容。没有源规范的绑定使用 DataContext 作为它们的源。您可以使用 Snoop 之类的东西来查看您的单元格和/或行上的 DataContext 是什么吗?
  • 是的,我已经解决了。一种选择是遵循视觉树。最简单的选择是,单元格的 DataContext 对象是一个类,该类在 Value 属性中具有单元格的值,并且在 Row 属性中还具有行对象(ItemClass 实例)。所以使用它是一件非常简单的事情。
【解决方案2】:

这就是答案[1]。 FindAncestor在运行时 XAML 树中而不是在任意 C# 对象中查找祖先。它不能从我们绑定的成员走到 ItemClass 实例。但我们确实知道 XAML 树中我们上面的某个人将我们绑定到该成员,并且他绑定到 ItemClass 实例本身。所以不管是谁,我们找到他,然后我们就有了 ItemClass。

因此,让我们将调试跟踪添加到绑定中,我们将看到 XAML 情况在运行时的样子。毫无疑问,还有其他可能更聪明的方法可以做到这一点,但我碰巧知道这个没有任何研究。

首先将其添加到 XAML 文件顶部的命名空间:

xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"

...然后到绑定本身,添加:

diag:PresentationTraceSources.TraceLevel=High

像这样:

<TextBox Text="{Binding Value, Mode=TwoWay}"
     IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}, diag:PresentationTraceSources.TraceLevel=High}"
/>

在运行时,当 TextEdit 的 IsEnabled 属性尝试从绑定中获取值时,绑定会向上遍历 XAML 树以查找指定类型的祖先。它一直在寻找,直到找到一个或用完一棵树,如果我们对其进行跟踪,它会一直跟踪它找到的所有内容的类型。我们已经告诉它寻找它永远找不到的垃圾,所以它会给我们一个追溯到树根的每个祖先的类型的踪迹,首先是叶子,最后是根。在这种情况下,我得到了 75 行祖先。

我这样做了,并找到了一些可能的候选人。我检查了每一个,结果获胜者是 dgx:GridCellContentPresenter,它有一个 RowData 属性。 RowData 有很多属性,RowData.Row 是行的ItemClass 的实例。 dxg:GridCellContentPresenter 属于我们正在使用的 DevExpress 网格库;在另一个供应商的网格类中,可能会有一些等价物。

这是有效的绑定:

<TextBox Text="{Binding Value, Mode=TwoWay}"
    IsEnabled="{Binding Path=RowData.Row.IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dxg:GridCellContentPresenter}, AncestorLevel=1}}"
/>

如果供应商 DevExpress 重写了他们的 GridControl 类,我们就会遇到麻烦。但无论如何,那是真的。

...

[1] 更好的答案,尽管它过于具体到 DevExpress,没有任何真正的兴趣:TextBox 本身的 DataContext 原来是 dxg:EditGridCellData,它具有 RowData 属性,就像 GridCellContentPresenter 一样。我可以使用 IsEnabled="{Binding Path=RowData.Row.IsEditAllowed}"。

然而,我一直以来真正想做的不是呈现一个充满愚蠢的禁用文本框的网格,而是启用对网格中某些行的编辑。 DevExpress 网格允许您通过 ShowingEditor 事件来做到这一点。

XAML:

<dxg:GridControl Name="grdItems">
    <dxg:GridControl.View>
        <dxg:TableView
            NavigationStyle="Cell"
            AllowEditing="True"
            ShowingEditor="grdItems_TableView_ShowingEditor"
            />
    </dxg:GridControl.View>
<!-- ... Much XAML ... -->
</dxg:GridControl Name="grdItems">

.cs:

private void grdItems_TableView_ShowingEditor(object sender, ShowingEditorEventArgs e)
{
    e.Cancel = !(e.Row as ItemClass).IsEditAllowed;
}

【讨论】:

    猜你喜欢
    • 2012-02-06
    • 2010-11-14
    • 2012-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-22
    • 2011-02-28
    相关资源
    最近更新 更多