特殊附加 属性 绑定有效,但在 DataTemplate 中无效
Special attached property binding works, but not in a DataTemplate
目标
我的目标是能够通过绑定为我的代码提供对 UI 元素的引用(而不是为元素提供 Name
或必须手动遍历可视化树找到它)。
为此,我创建了一个名为 Self
的特殊附加依赖项 属性。它基于 this answer 中的代码。它旨在以两种方式变得特别:
Self
的值应该始终是对其设置的元素的引用。因此,如果 Self
用于 Button
,Self
的值应始终 return 引用所述 Button
.
Self
属性 绑定时,应将其值推送到绑定源。
基本上,您应该能够做到这一点:
<Button Name="A" local:BindingHelper.Self="{Binding Foo.Button}"/>
然后 Foo.Button
将被赋予 Button
A
对象作为其值。
主要代码
为此,我改编了 previously mentioned answer 中的代码并创建了这个:
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
.
现在,注释掉 A
行并取消注释 B
行。从理论上讲,它应该完全相同,我所做的就是将 Button
移动到 DataTemplate
,但由于某种原因,Foo.Button
从未被引用 Button
] B
。这是我需要帮助弄清楚的部分。如果无法在 DataTemplate
中使用它,我将永远无法在 ItemsControl
中使用它。
到目前为止我的进度
问题似乎与以下问题有关:
Dim B = BindingOperations.GetBindingExpression(d, SelfProperty)
这意外地 returns Nothing
for Button
B
,所以 UpdateSource
永远不会被调用。 initialization/loading 完成后,如果我尝试从断点调用 GetBindingExpression
,它会 returns 预期值,但无论出于何种原因,当目标在内部初始化时它不会这样做DataTemplate
.
你应该能够让这个工作通过告诉 ContentControl
在哪里找到 Foo
属性 通过添加:
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}
<ContentControl Content="{Binding Foo, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
其他一切都与上面的示例完全相同。
当时我无法弄清楚为什么 BindingOperations.GetBindingExpression
returning Nothing
,但我想出了一个可靠的解决方法。我做了一些广泛的测试并绘制了调用堆栈,记录了 GetBindingExpression
何时开始 return 一个值。我发现在我的设置中,Self_PropertyChanged
会被调用两次,但 GetBindingExpression
只会在第二轮起作用。所以我添加了代码来跟踪在上一次调用 Self_PropertyChanged
期间是否存在绑定,如果不存在但现在确实存在,那么我会再次 运行 通过更新过程。
这里是所有感兴趣的人的完整工作代码:
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
的值,如果说源属性 初始化后更改(前提是源支持更改通知)。
有趣的是,通过我的测试,我发现调用 UpdateSource
是完全没有必要的,因为 SetSelf
总是导致源被更新。我删除了那个调用,它消除了对源 Set
和 Get
以及 Self_CoerceValue
.
的不必要的一轮调用
目标
我的目标是能够通过绑定为我的代码提供对 UI 元素的引用(而不是为元素提供 Name
或必须手动遍历可视化树找到它)。
为此,我创建了一个名为 Self
的特殊附加依赖项 属性。它基于 this answer 中的代码。它旨在以两种方式变得特别:
Self
的值应该始终是对其设置的元素的引用。因此,如果Self
用于Button
,Self
的值应始终 return 引用所述Button
.Self
属性 绑定时,应将其值推送到绑定源。
基本上,您应该能够做到这一点:
<Button Name="A" local:BindingHelper.Self="{Binding Foo.Button}"/>
然后 Foo.Button
将被赋予 Button
A
对象作为其值。
主要代码
为此,我改编了 previously mentioned answer 中的代码并创建了这个:
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
.
现在,注释掉 A
行并取消注释 B
行。从理论上讲,它应该完全相同,我所做的就是将 Button
移动到 DataTemplate
,但由于某种原因,Foo.Button
从未被引用 Button
] B
。这是我需要帮助弄清楚的部分。如果无法在 DataTemplate
中使用它,我将永远无法在 ItemsControl
中使用它。
到目前为止我的进度
问题似乎与以下问题有关:
Dim B = BindingOperations.GetBindingExpression(d, SelfProperty)
这意外地 returns Nothing
for Button
B
,所以 UpdateSource
永远不会被调用。 initialization/loading 完成后,如果我尝试从断点调用 GetBindingExpression
,它会 returns 预期值,但无论出于何种原因,当目标在内部初始化时它不会这样做DataTemplate
.
你应该能够让这个工作通过告诉 ContentControl
在哪里找到 Foo
属性 通过添加:
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}
<ContentControl Content="{Binding Foo, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
其他一切都与上面的示例完全相同。
当时我无法弄清楚为什么 BindingOperations.GetBindingExpression
returning Nothing
,但我想出了一个可靠的解决方法。我做了一些广泛的测试并绘制了调用堆栈,记录了 GetBindingExpression
何时开始 return 一个值。我发现在我的设置中,Self_PropertyChanged
会被调用两次,但 GetBindingExpression
只会在第二轮起作用。所以我添加了代码来跟踪在上一次调用 Self_PropertyChanged
期间是否存在绑定,如果不存在但现在确实存在,那么我会再次 运行 通过更新过程。
这里是所有感兴趣的人的完整工作代码:
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
的值,如果说源属性 初始化后更改(前提是源支持更改通知)。
有趣的是,通过我的测试,我发现调用 UpdateSource
是完全没有必要的,因为 SetSelf
总是导致源被更新。我删除了那个调用,它消除了对源 Set
和 Get
以及 Self_CoerceValue
.