lmhcs 2024-03-08 11:44 采纳率: 100%
浏览 6
已结题

DataContext 比附加属性慢执行

我创建一个附加属性来统一管理页面上的按钮状态。在调试中,发现附加属性先于xaml页面的DataContext属性绑定,导致出现绑定错误

错误
System.Windows.Data Error: 40 : BindingExpression path error: 'Text' property not found on 'object' ''CIconTexts' (HashCode=13213278)'. BindingExpression:Path=Text; DataItem='CIconTexts' (HashCode=13213278); target element is 'Button' (Name='btnMoveDown'); target property is 'ToolTip' (type 'Object')

我简单写个测试程序,发现确实附加属性先于datacontext绑定执行。百度了许久,没有找到xaml调整各个属性执行顺序的方法。特来论坛请教大家。
测试类

Public Class Items
    Property Text As String
    Property Item As item
    Public Overrides Function ToString() As String
        Return Text
    End Function
End Class
Public Class item
    Property Text As String

    Public Overrides Function ToString() As String
        Return Text
    End Function
End Class

附加属性管理类

Public Class manage


#Region "*** attached1 附加依赖属性 ****"

    ''' <summary>
    ''' 从指定元素获取 attached1 依赖项属性的值。
    ''' </summary>
    ''' <param name="obj">从中读取属性值的元素。</param>
    ''' <returns>从属性存储获取的属性值。</returns>
    Public Shared Function Getattached1(obj As DependencyObject) As String
        Return obj.GetValue(attached1Property)
    End Function


    ''' <summary>
    ''' 将 attached1 依赖项属性的值设置为指定元素。
    ''' </summary>
    ''' <param name="obj">对其设置属性值的元素。</param>
    ''' <param name="value">要设置的值。</param>
    Public Shared Sub Setattached1(obj As DependencyObject, value As String)
        obj.SetValue(attached1Property, value)
    End Sub

    ''' <summary>
    ''' 标识 attached1 依赖项属性。
    ''' </summary>
    Public Shared ReadOnly attached1Property As DependencyProperty =
        DependencyProperty.RegisterAttached("attached1", GetType(String), GetType(manage),
                                             New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf Onattached1PropertyChangedBackCall)))


    Private Shared Sub Onattached1PropertyChangedBackCall(d As DependencyObject, args As DependencyPropertyChangedEventArgs)
        Dim fe As FrameworkElement = TryCast(d, FrameworkElement)
        MsgBox($" { fe.DataContext  }")
        Dim oldValue As String = args.OldValue
        Dim newValue As String = args.NewValue
        If oldValue = newValue Then Return


        'Dim target As manage = TryCast(d, manage)
        'target?.Onattached1Changed(newValue, oldValue)
    End Sub

#End Region
End Class

创建一个自定义按钮并创建一个依赖属性

Imports System.Windows.Controls.Primitives
Public Class testButton
    Inherits System.Windows.Controls.Button
    Shared Sub New()
        '此 OverrideMetadata 调用通知系统该元素希望提供不同于其基类的样式。
        '此样式定义在 themes\generic.xaml 中
        DefaultStyleKeyProperty.OverrideMetadata(GetType(testButton), new FrameworkPropertyMetadata(GetType(testButton)))
    End Sub
#Region "   ***** icon 依赖属性 *****"
    ''' <summary>
    ''' 获取或设置 属性描述 的值
    ''' </summary> 
    '''<remarks>
    ''' 
    '''</remarks>
    Public Property icon As String
        Get
            Return GetValue(iconProperty)
        End Get
        Set(ByVal value As String)
            SetValue(iconProperty, value)
        End Set
    End Property
    ''' <summary>
    ''' 标识 icon 依赖属性。
    ''' </summary>
    Public Shared ReadOnly iconProperty As DependencyProperty =
                             DependencyProperty.Register("icon",
                             GetType(String), GetType(testButton),
                             New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf iconPropertyChangedBackCall)))
    ''' <summary>
    ''' icon 属性更改时回调此方法。
    '''</summary>
    Private Shared Sub iconPropertyChangedBackCall(d As DependencyObject, dp As DependencyPropertyChangedEventArgs)
        Dim newValue As String = TryCast(dp.NewValue, String)
        Dim oldValue As String = TryCast(dp.OldValue, String)
        If Object.Equals(newValue, oldValue) Then Return
        Dim target As testButton = TryCast(d, testButton)
        target?.OniconChanged(newValue, oldValue)
    End Sub
    ''' <summary>
    ''' icon 属性更改时调用此方法。
    '''</summary>
    ''' <param name="oldValue"> 属性的旧值。</param>
    ''' <param name="newValue"> 属性的新值。</param>
    Protected Overridable Sub OniconChanged(newValue As String, oldValue As String)
        MsgBox("icon被设置")
    End Sub
#End Region
End Class

generic

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:依赖属性和附加属性同时设置的执行顺序">


    <Style TargetType="{x:Type local:testButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:testButton}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <ContentPresenter />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

测试页面

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:依赖属性和附加属性同时设置的执行顺序"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <local:Items Text ="items" x:Key="t1" >
            <local:Items.Item  >
                <local:item Text="item" ></local:item>
            </local:Items.Item>
        </local:Items>
    </Window.Resources>
    <Grid DataContext="{StaticResource t1 }">
        <Grid.RowDefinitions >
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <!-- 根据运行情况来看,哪个在前面,哪个先运行-->
        <!--<local:testButton x:Name ="testbtn1" icon="a"  local:manage.attached1="bb"   />-->
        
        
        <!-- 根据运行情况来看,下面代码附加属性先运行,datacontext慢运行-->
        <local:testButton x:Name ="testbtn2" DataContext="{Binding Path=Item }"   local:manage.attached1="bb"   />
        <Button Content="test" Click="Button_Click" Grid.Row="1"/>
    </Grid>
</Window>

后台代码

Class MainWindow
    Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
        MsgBox(testbtn2.DataContext.ToString)
    End Sub
End Class


  • 写回答

3条回答 默认 最新

  • 专家-赤兔[在线] 优质创作者: 编程框架技术领域 2024-03-08 11:45
    关注

    引自免费微信小程序:皆我百晓生

    在WPF中,附加属性和依赖属性(包括DataContext)的初始化顺序是由XAML加载机制决定的,而不是由我们直接控制。当XAML解析器处理元素时,它会首先创建控件实例并设置附加属性,然后才设置依赖属性,最后执行DataContext绑定。因此,在您的测试场景中,附加属性manage.attached1确实会在DataContext被赋值之前进行处理。

    针对您遇到的问题,Onattached1PropertyChangedBackCall回调中的fe.DataContext尚未被设置,所以出现了“Text”属性未找到的错误。这是因为尝试访问DataContext时,其还未绑定到实际的数据对象。

    解决办法是在附加属性的变更回调中检查FrameworkElement.DataContext是否已准备好。如果DataContext为空,则可以使用Dispatcher来延迟执行对DataContext的访问,直到数据上下文已经完成绑定:

    Private Shared Sub Onattached1PropertyChangedBackCall(d As DependencyObject, args As DependencyPropertyChangedEventArgs)
        Dim fe As FrameworkElement = TryCast(d, FrameworkElement)
    
        ' 检查 DataContext 是否已设置,如果没有则等待其准备就绪后再执行操作
        If fe IsNot Nothing AndAlso fe.DataContext IsNot Nothing Then
            MsgBox($" {fe.DataContext}")
        Else
            ' 使用 Dispatcher 来延后执行,直到 DataContext 设置完毕
            fe.Dispatcher.BeginInvoke(Sub()
                                          If fe IsNot Nothing AndAlso fe.DataContext IsNot Nothing Then
                                              MsgBox($" {fe.DataContext}")
                                          End If
                                      End Sub,
                                      DispatcherPriority.DataBind)
        End If
    
        ' ... 其他代码保持不变 ...
    End Sub
    

    另外,请确保Items.Text属性或item.Text属性能够在数据上下文链路中正确找到。根据错误信息,似乎尝试从CIconTexts类中查找Text属性,但该类并没有这个属性。请核实您在视图模型层以及XAML绑定路径上是否有误。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论 编辑记录
查看更多回答(2条)

报告相同问题?

问题事件

  • 系统已结题 3月16日
  • 已采纳回答 3月8日
  • 创建了问题 3月8日