wpf XAML 绑定到数据对象的父级

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16449708/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:45:19  来源:igfitidea点击:

XAML Binding to parent of data object

wpfxamldata-bindingbinding

提问by 15ee8f99-57ff-4f92-890c-b56153

I have a grid column defined. The parent grid gets its items from an ObservableCollection of type ItemClass. ItemClass has two properties: String Foo, and bool IsEditAllowed.

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

This column is bound to property Foo. There's a control template for editing the cell. I'd like to bind the ItemClass.IsEditAllowed property to the IsEnabled property of the TextBox in the template.

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

The question is how to bind it. Can this be done? The XAML below gets me "Cannot find source for binding with reference" in the debug trace.

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

The grid will let me bind the ItemClass itself to the field via some "custom" event thingy, and I can then bind to any of its properties. That's fine, but it seems kludgy. But if it's the only way, it's the only way.

网格会让我通过一些“自定义”事件将 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>

回答by dtm

There are two potentially easier ways to set up this binding.

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

1) name the grid. Then your binding could look something like this (assuming dxg:GridControl has a property named "Items" and that you have assigned an instance of your ItemClass to that property):

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

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

2) use relative binding, but look for the GridControl rather than something nominally internal to the way GridControl works (that is, GridControlContentPresenter). This gets you away from the implementation details of GridControl, which are perhaps more likely to change in ways that break your application than are properties on GridControl itself.

2) 使用相对绑定,但寻找 GridControl 而不是 GridControl 工作方式名义上的内部(即 GridControlContentPresenter)。这会让您远离 GridControl 的实现细节,与 GridControl 本身的属性相比,这些细节可能更容易以破坏应用程序的方式进行更改。

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

You may also want to read up on the Visual Tree and the Logical Treein WPF/xaml. The "Ancestor" in relative bindings refers to ancestors in the visual tree, that is, things like parent containers, and not to super- or base classes (as you've discovered, I think).

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

回答by 15ee8f99-57ff-4f92-890c-b56153

Here's the answer[1]. FindAncestor finds ancestors in the runtime XAML tree, not in arbitrary C# objects. It cannot walk up to the ItemClass instance from the member we're bound to. But we do know that somebody above us in the XAML tree bound us to that member, and he was bound to the ItemClass instance itself. So whoever that is, we find him, and then we've got the ItemClass.

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

So let's add debug tracing to the binding, and we'll see what the XAML situation looks like at runtime. No doubt there are other and probably smarter ways to do that, but I happen to know this one without any research.

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

First add this to the namespaces at the top of the XAML file:

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

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

...and then to the binding itself, add this:

...然后到绑定本身,添加以下内容:

diag:PresentationTraceSources.TraceLevel=High

Like so:

像这样:

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

At runtime, when the TextEdit's IsEnabled property tries to get a value from the binding, the binding walks up through the XAML tree looking for an ancestor of the specified type. It keeps looking until it finds one or runs out of tree, and if we put tracing on it, it traces the type of everything it finds the whole way up. We've told it to look for garbage that it'll never find, so it will give us a trace of the type of every ancestor back to the root of the tree, leaf first and root last. I get 75 lines of ancestors in this case.

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

I did that, and found a few likely candidates. I checked each one, and the winner turned out to be dgx:GridCellContentPresenter, which has a RowData property. RowData has a lot of properties, and RowData.Row is the row's instance of ItemClass. dxg:GridCellContentPresenter belongs to the DevExpress grid library we're using; in another vendor's grid class, there would presumably be some equivalent.

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

Here's the working binding:

这是工作绑定:

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

If DevExpress, the vendor, rewrites their GridControl class, we'll be in trouble. But that was true anyhow.

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

...

...

[1] Better answer, though it's too DevExpress specific to be of any real interest: The DataContext of the TextBox itself turns out to be dxg:EditGridCellData, which has a RowData property just like GridCellContentPresenter does. I can just use IsEnabled="{Binding Path=RowData.Row.IsEditAllowed}".

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

However, what I really wanted to do all along was not to present a grid full of stupid disabled textboxes, but rather to enable editing on certain rows in the grid. And the DevExpress grid lets you do that through the ShowingEditor event.

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

XAML:

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:

。CS:

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