VB.NET数据绑定标签没有监听PropertyChanged

时间:2014-01-07 16:23:46

标签: vb.net winforms data-binding

我有一个标签可以数据绑定到Class中的Property,它返回另一个类中的最大数字。这些数字从6个文本字段填充到另一个类中。

当使用参数“BestThrow.Distance”修改时,数字的6个属性都会引发PropertyChanged事件,如下所示:


    Public Property Distance() As String
        Get
            If Status = ThrowStatus.Valid Then
                If (m_Distance > 0) Then
                    Return m_Distance
                Else
                    Return Nothing
                End If

            ElseIf Status = ThrowStatus.Pass Then
                Return "PASS"
            ElseIf Status = ThrowStatus.Foul Then
                Return "FOUL"
            Else
                Return Nothing
            End If

        End Get
        Set(value As String)
            If (value.Length > 0) Then
                If (IsNumeric(value)) Then
                    m_Distance = value
                    Status = ThrowStatus.Valid
                ElseIf (value = "FOUL") Then
                    Status = ThrowStatus.Foul
                ElseIf (value = "PASS") Then
                    Status = ThrowStatus.Pass
                Else
                    Status = ThrowStatus.Valid
                    m_Distance = Nothing
                End If
            Else
                m_Distance = Nothing
                Status = ThrowStatus.Valid
            End If
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("BestThrow.Distance"))
        End Set
    End Property

标签所绑定的属性如下:


    Public ReadOnly Property BestThrow() As Object
        Get
            Dim bt = Throws.Where(Function(t) t.Status = ThrowStatus.Valid).OrderByDescending(Function(t) t.Distance).First()
            If (IsNothing(bt.Distance)) Then
                bt.Distance = "0"
            End If

            Return bt
        End Get
    End Property

为了提供所有信息,标签的数据绑定如下 best.DataBindings.Add(New Binding("text", athlete, "BestThrow.Distance", False, DataSourceUpdateMode.OnPropertyChanged))

当我添加第一个距离时,标签会更新,并按预期显示距离(0)。距离,但是当我更改其他5个值时,标签将不会更新。但是,如果我再次修改第一个值,它将重新计算BestThrow并显示正确的距离。

我还尝试使用表单上的按钮手动提升事件,该按钮也无效。

从使用观察值我可以看到,当请求BestThrow属性吐出正确的值时,看起来标签只对第一次投掷的事件感兴趣。

提前感谢你的帮助。

当前代码:



    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices

    Public Class Competition
        Public Sub New()
            Competitors = New List(Of Competitor)()
        End Sub
        Public Property Competitors() As List(Of Competitor)
            Get
                Return m_Competitors
            End Get
            Set(value As List(Of Competitor))
                m_Competitors = value
            End Set
        End Property
        Private m_Competitors As List(Of Competitor)
        Public ReadOnly Property CurrentPlacings() As List(Of Competitor)
            Get
                Return Competitors.OrderByDescending(Function(c) c.BestThrow).ToList()


            End Get
        End Property

    End Class
    Public Class Competitor

        Public Sub New()
            Throws = New List(Of [Throw])()
        End Sub
        Public Property athletenum() As Integer
            Get
                Return m_athnum
            End Get
            Set(value As Integer)
                m_athnum = value
            End Set
        End Property
        Private m_Athnum As Integer

        Public Property FirstName() As String
            Get
                Return m_FirstName
            End Get
            Set(value As String)
                m_FirstName = value
            End Set
        End Property
        Private m_FirstName As String
        Public Property LastName() As String
            Get
                Return m_LastName
            End Get
            Set(value As String)
                m_LastName = value
            End Set
        End Property
        Private m_LastName As String
        Public Property compNumber() As String
            Get
                Return m_compNumb
            End Get
            Set(value As String)
                m_compNumb = value
            End Set
        End Property
        Private m_compNumb As String
        Public Property club() As String
            Get
                Return m_club
            End Get
            Set(value As String)
                m_club = value
            End Set
        End Property
        Private m_club As String
        Public Property Throws() As List(Of [Throw])
            Get
                Return m_Throws
            End Get
            Set(value As List(Of [Throw]))
                m_Throws = value
            End Set
        End Property
        Private m_Throws As List(Of [Throw])
        Public ReadOnly Property BestThrow() As String
            Get
                Dim list As IList(Of [Throw]) = (From t As [Throw] In Throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Descending).ToList()
                If (list.Count > 0) Then
                    If (IsNothing(list(0).Distance)) Then
                        Return "0"
                    Else
                        Return list(0).Distance
                    End If

                End If
                Return "0"
            End Get
        End Property
        Public ReadOnly Property getLabel()
            Get
                Return compNumber & " " & LastName & ", " & FirstName & vbCrLf & club
            End Get
        End Property
        Public Property currentPlace
            Get
                Return m_curplace
            End Get
            Set(value)
                m_curplace = value
            End Set

        End Property
        Private m_curplace As Integer
    End Class
    Public Enum ThrowStatus
        Valid
        Pass
        Foul
    End Enum
    Public Class [Throw]
        Implements INotifyPropertyChanged
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        'Throw Status
        Public Property Status() As ThrowStatus
            Get
                Return m_Status
            End Get
            Set(value As ThrowStatus)
                m_Status = value
            End Set
        End Property
        Private m_Status As ThrowStatus

        'Throw Distance
        Public Property Distance() As String
            Get
                Dim value As String = Me.m_Distance
                If Status = ThrowStatus.Valid Then
                    If (m_Distance > 0) Then
                        value = m_Distance
                    Else
                        value = Nothing
                    End If

                ElseIf Status = ThrowStatus.Pass Then
                    value = "PASS"
                ElseIf Status = ThrowStatus.Foul Then
                    value = "FOUL"
                Else
                    value = Nothing
                End If
                If (value  Me.m_Distance) Then
                    Me.Distance = value
                End If
                Return value
            End Get
            Set(value As String)
                If (value.Length > 0) Then
                    If (IsNumeric(value)) Then
                        m_Distance = value
                        Status = ThrowStatus.Valid
                    ElseIf (value = "FOUL") Then
                        Status = ThrowStatus.Foul
                    ElseIf (value = "PASS") Then
                        Status = ThrowStatus.Pass
                    Else
                        Status = ThrowStatus.Valid
                        m_Distance = Nothing
                    End If
                Else
                    m_Distance = Nothing
                    Status = ThrowStatus.Valid
                End If
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("BestThrow"))
            End Set
        End Property
        Private m_Distance As Decimal
        Public Property attempt() As Integer
            Get
                Return m_attempt
            End Get
            Set(value As Integer)
                m_attempt = value
            End Set
        End Property
        Private m_attempt As Integer

    End Class

当前绑定: best.DataBindings.Add(New Binding("text", athlete, "BestThrow", False, DataSourceUpdateMode.OnPropertyChanged))

1 个答案:

答案 0 :(得分:0)

首先,我不喜欢看到计算返回值的属性。我认为这应该在一个方法中完成。 (在只读属性中可能没问题。)只是我的意见。我觉得很奇怪的是Distance的吸气剂取决于Status,并且设定器更像是Status的设定器。

您的绑定需要PropertyDescriptor名为Distance。您返回String,如您所知,字符串类没有名为Distance的属性。

best.DataBindings.Add(New Binding("text", athlete, "BestThrow.Distance", False, DataSourceUpdateMode.OnPropertyChanged))

选项1

为此,你需要这样做:

Public ReadOnly Property BestThrow() As TYPE_OF_YOUR_CLASS
    Get
        'IMPORTANT: 
        'This approach requires at least ONE matching object and 
        'ONE item returned. If not, the binding will fail.
        Return (From t As TYPE_OF_YOUR_CLASS In throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Ascending).First()
    End Get
End Property

选项2

另一种选择是绑定到BestThrow属性:

best.DataBindings.Add(New Binding("text", athlete, "BestThrow", False, DataSourceUpdateMode.OnPropertyChanged))

将您的财产更改为:

Public ReadOnly Property BestThrow() As String
    Get
        'NOTE: 
        'Do not use First() as this will fail if no matching items are found.
        'We use ToList() and check the count property.
        Dim list As IList(Of TYPE_OF_YOUR_CLASS) = (From t As TYPE_OF_YOUR_CLASS In throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Ascending).ToList()
        If (list.Count > 0) Then
            Return list(0).Distance
        End If
        Return "0"
    End Get
End Property

修改

老实说,我认为这是糟糕的设计。请详细了解这种方法:

Public Class Athlete
    'TODO: Implements INotifyPropertyChanged

    Public Sub New(athleteNumber As Integer, Optional firstName As String = Nothing, Optional ByVal lastName As String = Nothing, Optional ByVal club As String = Nothing)
        Me.m_athleteNumber = athleteNumber 
        Me.m_firstName = If((firstName Is Nothing), String.Empty, firstName)
        Me.m_lastName = If((lastName Is Nothing), String.Empty, lastName)
        Me.m_club = If((club Is Nothing), String.Empty, club)
    End Sub

    Public ReadOnly Property AthleteNumber() As Integer
        Get
            Return Me.m_athleteNumber
        End Get
    End Property

    Public Property Club() As String
        Get
            Return Me.m_club
        End Get
        Set(value As String)
            Me.m_club = value
            'TODO: If changed raise property changed.
        End Set
    End Property

    Public Property FirstName() As String
        Get
            Return Me.m_firstName
        End Get
        Set(value As String)
            Me.m_firstName = value
            'TODO: If changed raise property changed.
        End Set
    End Property

    Public Property LastName() As String
        Get
            Return Me.m_lastName
        End Get
        Set(value As String)
            Me.m_lastName = value
            'TODO: If changed raise property changed.
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return String.Concat(Me.m_firstName, " ", Me.m_lastName).Trim()
    End Function

    Private m_athleteNumber As Integer
    Private m_club As String
    Private m_firstName As String
    Private m_lastName As String

End Class

Public Class Competitor
    Implements INotifyPropertyChanged

    Public Sub New(athlete As Athlete, Optional competitorNumber As String = Nothing)
        If (athlete Is Nothing) Then
            Throw New ArgumentNullException("athlete")
        End If
        Me.m_athlete = athlete
        Me.m_bestThrow = 0D
        Me.m_competitorNumber = If((competitorNumber Is Nothing), String.Empty, competitorNumber)
        Me.m_curplace = 0
        Me.m_throws = New ObjectCollection(Of [Throw])
        AddHandler Me.m_throws.CollectionChanged, New CollectionChangeEventHandler(AddressOf Me.OnThrowCollectionChanged)
    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public ReadOnly Property Athlete() As Athlete
        Get
            Return Me.m_athlete
        End Get
    End Property

    Public ReadOnly Property BestThrow() As Decimal
        Get
            Return Me.m_bestThrow
        End Get
    End Property

    Public Property CompetitorNumber() As String
        Get
            Return Me.m_competitorNumber
        End Get
        Set(value As String)
            If (value <> Me.m_competitorNumber) Then
                Me.m_competitorNumber = value
                Me.RaisePropertyChanged("CompetitorNumber")
            End If
        End Set
    End Property

    Public Property CurrentPlace() As Integer
        Get
            Return Me.m_curplace
        End Get
        Set(value As Integer)
            If (value <> Me.m_curplace) Then
                Me.m_curplace = value
                Me.RaisePropertyChanged("CurrentPlace")
            End If
        End Set
    End Property

    Public ReadOnly Property Throws() As ObjectCollection(Of [Throw])
        Get
            Return Me.m_throws
        End Get
    End Property

    Protected Sub OnThrowCollectionChanged(sender As Object, e As CollectionChangeEventArgs)
        Dim list As IList(Of [Throw]) = (From t As [Throw] In Me.m_throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Descending).ToList()
        Dim bestThrow As Decimal = If((list.Count > 0), list(0).Distance, 0D)
        If (Me.m_bestThrow <> bestThrow) Then
            Me.m_bestThrow = bestThrow
            Me.RaisePropertyChanged("BestThrow")
        End If
    End Sub

    Private Sub RaisePropertyChanged(propertyName As String)
        If (Not Me.PropertyChangedEvent Is Nothing) Then
            Me.PropertyChangedEvent.Invoke(Me, New PropertyChangedEventArgs(propertyName))
        End If
    End Sub

    Private m_athlete As Athlete
    Private m_bestThrow As Decimal
    Private m_competitorNumber As String
    Private m_curplace As Integer
    Private m_throws As ObjectCollection(Of [Throw])

End Class

Public Class [Throw]

    Public Sub New(Optional ByVal status As ThrowStatus = ThrowStatus.Valid, Optional distance As Decimal = 0D)
        Me.m_status = status
        Me.m_distance = Math.Max(distance, 0D)
    End Sub

    Public ReadOnly Property Status() As ThrowStatus
        Get
            Return Me.m_status
        End Get
    End Property

    Public ReadOnly Property Distance() As Decimal
        Get
            Return Me.m_distance
        End Get
    End Property

    Public Overrides Function ToString() As String
        Select Case Me.m_status
            Case ThrowStatus.Valid
                Return Me.m_distance.ToString("N2")
            Case ThrowStatus.Pass
                Return "PASS"
            Case ThrowStatus.Foul
                Return "FOUL"
            Case Else
                Return 0D.ToString("N2")
        End Select
    End Function

    Private m_attempt As Integer
    Private m_distance As Decimal
    Private m_status As ThrowStatus

End Class

Public Class ObjectCollection(Of T)
    Inherits Collections.ObjectModel.Collection(Of T)

    Public Event CollectionChanged As CollectionChangeEventHandler

    Protected Overrides Sub ClearItems()
        MyBase.ClearItems()
        Me.RaiseCollectionChanged(CollectionChangeAction.Refresh, Nothing)
    End Sub

    Protected Overrides Sub InsertItem(index As Integer, item As T)
        If (item Is Nothing) Then
            Throw New ArgumentNullException("item")
        ElseIf (Me.Contains(item)) Then
            Throw New ArgumentException("Item duplicate.", "item")
        End If
        MyBase.InsertItem(index, item)
        Me.RaiseCollectionChanged(CollectionChangeAction.Add, item)
    End Sub

    Protected Overridable Sub OnCollectionChanged(e As CollectionChangeEventArgs)
        If (Not Me.CollectionChangedEvent Is Nothing) Then
            Me.CollectionChangedEvent.Invoke(Me, e)
        End If
    End Sub

    Private Sub RaiseCollectionChanged(action As CollectionChangeAction, item As T)
        Me.OnCollectionChanged(New CollectionChangeEventArgs(action, item))
    End Sub

    Protected Overrides Sub RemoveItem(index As Integer)
        Dim item As T = Me.Item(index)
        MyBase.RemoveItem(index)
        Me.RaiseCollectionChanged(CollectionChangeAction.Remove, item)
    End Sub

    Protected Overrides Sub SetItem(index As Integer, item As T)
        Throw New NotSupportedException()
    End Sub

End Class

Public Enum ThrowStatus As Integer
    Valid = 0 '<- This is the default value.
    Pass = 1
    Foul = 2
End Enum

以下代码经过测试并可以使用。将ButtonTextBox放到Form上并附加:

Public Class Form1

    Public Sub New()

        Me.InitializeComponent()

        Me.competitor = New Competitor(New Athlete(1, "John", "Smith", "Team JS"), "CP_1")
        Me.competitor.Throws.Add(New [Throw](distance:=123.3472D))
        Me.competitor.Throws.Add(New [Throw](distance:=424.1234D))
        Me.competitor.Throws.Add(New [Throw](distance:=242.1234D))

        Dim b As New Binding("Text", Me.competitor, "BestThrow", True, DataSourceUpdateMode.Never)
        AddHandler b.Format, New ConvertEventHandler(AddressOf Me._FormatBinding)
        Me.TextBox1.DataBindings.Add(b)

    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Me.competitor.Throws.Add(New [Throw](distance:=845.4365D))
    End Sub

    Private Sub _FormatBinding(sender As Object, e As ConvertEventArgs)
        If ((TypeOf e.Value Is Decimal) AndAlso (e.DesiredType Is GetType(String))) Then
            e.Value = CDec(e.Value).ToString("N2")
        End If
    End Sub

    Private competitor As Competitor

End Class