特殊的附加属性绑定有效,但不适用于DataTemplate [英] Special attached property binding works, but not in a DataTemplate
问题描述
我的目标是能够通过绑定为我的代码提供对UI元素的引用(而不是给该元素一个 Name
或必须手动遍历可视化树来查找它)).
My goal is to be able to give my code a reference to a UI element via a binding (as opposed to giving the element a Name
or having to walk through the visual tree manually to find it).
为此,我创建了一个特殊的附加依赖项属性,称为 Self
.它基于此答案中的代码.它有两种特殊的用途:
To that end, I've created a special attached dependency property called Self
. It's based off the code from this answer. It's intended to be special in two ways:
-
Self
的值应始终是对其设置元素的引用.因此,如果在Button
上使用Self
,则Self
的值应始终返回对所述Button
的引用. -
Self
属性绑定后,应将其值压入绑定源.
- The value of
Self
should always be a reference to the element on which it is set. So ifSelf
is used on aButton
, the value ofSelf
should always return a reference to saidButton
. - The
Self
property, when bound to, should push its value onto the binding source.
基本上,您应该可以这样做:
Basically, you should be able to do this:
<Button Name="A" local:BindingHelper.Self="{Binding Foo.Button}"/>
然后,将为 Foo.Button
提供 Button
A
对象作为其值.
And Foo.Button
would then be given the Button
A
object as its value.
为此,我改编了先前提到的答案中的代码,并创建了以下代码:
To accomplish this, I've adapted the code from the previously mentioned answer and created this:
Public Class BindingHelper
Public Shared ReadOnly SelfProperty As DependencyProperty =
DependencyProperty.RegisterAttached("Self", GetType(DependencyObject), GetType(BindingHelper),
New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
AddressOf Self_PropertyChanged, AddressOf Self_CoerceValue))
Public Shared Function GetSelf(element As DependencyObject) As DependencyObject
Return element.GetValue(SelfProperty)
End Function
Public Shared Sub SetSelf(element As DependencyObject, value As DependencyObject)
element.SetValue(SelfProperty, value)
End Sub
Private Shared Sub Self_PropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
If e.NewValue IsNot d Then UpdateSelfValue(d)
End Sub
Private Shared SelfCoercionInProgress As New HashSet(Of DependencyObject)
Private Shared Function Self_CoerceValue(d As DependencyObject, baseValue As Object) As Object
If baseValue IsNot d AndAlso Not SelfCoercionInProgress.Contains(d) Then
SelfCoercionInProgress.Add(d)
UpdateSelfValue(d)
SelfCoercionInProgress.Remove(d)
End If
Return d
End Function
Private Shared Sub UpdateSelfValue(d As DependencyObject)
Dim B = BindingOperations.GetBindingExpression(d, SelfProperty)
If B IsNot Nothing AndAlso B.Status <> BindingStatus.Detached Then
B.UpdateTarget()
SetSelf(d, d)
B.UpdateSource()
Else
SetSelf(d, d)
End If
End Sub
End Class
用于测试和错误重现的代码
一个简单的 MainWindow.xaml.vb
:
Class MainWindow
Public Property Foo As New Foo
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
'You can put a breakpoint here
End Sub
End Class
Public Class Foo
Public Property Button As Button
End Class
和 MainWindow.xaml
:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VBTest"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<Button Name="A" local:BindingHelper.Self="{Binding Foo.Button}" Click="Button_Click">A</Button>
<ContentControl Content="{Binding Foo}">
<ContentControl.ContentTemplate>
<DataTemplate>
<!--<Button Name="B" local:BindingHelper.Self="{Binding Button}" Click="Button_Click">B</Button>-->
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</StackPanel>
</Window>
请注意, DataTemplate
中的 B
行已被注释掉.如果您运行上面的代码,它将按预期工作. Foo.Button
被赋予对 Button
A
的引用.
Notice that the B
line in the DataTemplate
is commented out. If you run the above code, it works as intended. Foo.Button
is given a reference to Button
A
.
现在,注释掉 A
行,并取消注释 B
行.从理论上讲,它应该工作完全相同,我所做的只是将 Button
移到 DataTemplate
中,但是由于某些原因,将 Foo.Button
永远不会引用 Button
B
.这是我需要帮助弄清楚的部分.无法在 DataTemplate
中使用它,我永远不能在 ItemsControl
中使用它.
Now, comment out the A
line and un-comment the B
line. In theory, it should work exactly the same, all I've done is move the Button
into a DataTemplate
, but for some reason Foo.Button
is never given a reference to Button
B
. This is the part I need help figuring out. Without being able to use this in a DataTemplate
, I can never use it in an ItemsControl
.
问题似乎与以下情况有关:
The problem seems to have something to do with:
Dim B = BindingOperations.GetBindingExpression(d, SelfProperty)
这意外地为 Button
B
返回 Nothing
,所以从不调用 UpdateSource
.初始化/加载完成后,如果我尝试从断点调用 GetBindingExpression
,它将返回期望值,但是由于某种原因,当在中初始化目标时,它不会这样做DataTemplate
.
This unexpectedly returns Nothing
for Button
B
, so UpdateSource
is never called. After initialization/loading is finished, if I try calling GetBindingExpression
from a breakpoint, it returns the expected value, but for whatever reason it doesn't do so when the target is being initialized inside a DataTemplate
.
推荐答案
我无法弄清楚 BindingOperations.GetBindingExpression
当时为何不返回 Nothing
的原因,但是我能够提出一个可靠的解决方法.我做了一些广泛的测试,并绘制出了调用堆栈,并记下了 GetBindingExpression
开始返回值的时间.我发现在我的设置中, Self_PropertyChanged
将被调用两次,但是 GetBindingExpression
仅在第二轮工作.因此,我添加了代码以跟踪在上一次调用 Self_PropertyChanged
的过程中是否存在绑定,如果现在不存在,则再次执行更新过程.
I couldn't figure out exactly why BindingOperations.GetBindingExpression
was returning Nothing
at that point, but I was able to come up with a reliable work-around. I did some extensive testing and mapped out the call stack, noting when GetBindingExpression
started to return a value. I found that in my setup, Self_PropertyChanged
would be called twice, but GetBindingExpression
would only work on the second round. So I added code to track whether or not a binding existed during the previous call to Self_PropertyChanged
, and if it didn't but one does exist now, then I run through the update procedure again.
这是所有感兴趣的人的完整工作代码:
Here's the full, working code for anyone interested:
Public Class BindingHelper
Public Shared ReadOnly SelfProperty As DependencyProperty =
DependencyProperty.RegisterAttached("Self", GetType(DependencyObject), GetType(BindingHelper),
New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
AddressOf Self_PropertyChanged, AddressOf Self_CoerceValue))
Public Shared Function GetSelf(element As DependencyObject) As DependencyObject
Return element.GetValue(SelfProperty)
End Function
Public Shared Sub SetSelf(element As DependencyObject, value As DependencyObject)
element.SetValue(SelfProperty, value)
End Sub
Private Shared Sub Self_PropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim NeedToChange = e.NewValue IsNot d
Dim B As BindingExpression
If NeedToChange OrElse HadNoBindingOnLastChange.Contains(d) Then
B = UpdateSelfValue(d, NeedToChange)
Else
B = GetActiveSelfBinding(d)
End If
If B Is Nothing Then
HadNoBindingOnLastChange.Add(d)
Else
HadNoBindingOnLastChange.Remove(d)
End If
End Sub
Private Shared SelfCoercionInProgress As New HashSet(Of DependencyObject)
Private Shared HadNoBindingOnLastChange As New HashSet(Of DependencyObject)
Private Shared UpdatingWithBindingInProgress As New HashSet(Of DependencyObject)
Private Shared Function Self_CoerceValue(d As DependencyObject, baseValue As Object) As Object
If baseValue IsNot d AndAlso Not SelfCoercionInProgress.Contains(d) Then
SelfCoercionInProgress.Add(d)
UpdateSelfValue(d, True)
SelfCoercionInProgress.Remove(d)
End If
Return d
End Function
Private Shared Function GetActiveSelfBinding(d) As BindingExpression
Dim B = BindingOperations.GetBindingExpression(d, SelfProperty)
If B IsNot Nothing AndAlso B.Status = BindingStatus.Detached Then Return Nothing
Return B
End Function
Private Shared Function UpdateSelfValue(d As DependencyObject, AlwaysSetSelf As Boolean) As BindingExpression
Dim B = GetActiveSelfBinding(d)
If B IsNot Nothing Then
If UpdatingWithBindingInProgress.Add(d) Then
B.UpdateTarget()
SetSelf(d, d)
UpdatingWithBindingInProgress.Remove(d)
End If
ElseIf AlwaysSetSelf Then
SetSelf(d, d)
End If
Return B
End Function
End Class
我已经对此进行了测试,它可以在 DataTempalte
的内部或外部运行,并且还可以将源属性自动更改回 Self
的值(如果所说的源)属性在初始化后更改(前提是源支持更改通知).
I've tested this, and it works inside or outside a DataTempalte
, and also works to automatically change the source property back to the value of Self
if said source property changes after initialization (provided the source supports change notification).
有趣的是,通过我的测试,我发现完全不需要调用 UpdateSource
,因为 SetSelf
总是导致源代码被更新.我删除了该调用,从而消除了对源的 Set
和 Get
以及 Self_CoerceValue
的不必要的调用.
Interestingly, through my testing I found that the call to UpdateSource
was completely unnecessary, since SetSelf
always resulted in the source being updated. I removed that call which eliminated an unnecessary round of calls to the source's Set
and Get
, as well as Self_CoerceValue
.
这篇关于特殊的附加属性绑定有效,但不适用于DataTemplate的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!