【发布时间】:2021-06-27 18:17:16
【问题描述】:
目标
我的目标是能够通过绑定为我的代码提供对 UI 元素的引用(而不是为元素提供 Name 或必须手动遍历可视化树才能找到它)。
为此,我创建了一个特殊的附加依赖属性,称为Self。它基于来自this answer 的代码。它旨在以两种方式特别:
-
Self的值应始终是对其设置的元素的引用。因此,如果在Button上使用Self,则Self的值应始终返回对所述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)
这会意外地为Button B 返回Nothing,因此永远不会调用UpdateSource。初始化/加载完成后,如果我尝试从断点调用 GetBindingExpression,它会返回预期值,但无论出于何种原因,当目标在 DataTemplate 内初始化时它不会这样做。
【问题讨论】:
标签: wpf vb.net data-binding dependency-properties