数据更改时更新数据绑定控件

时间:2015-08-14 10:29:37

标签: vb.net data-binding

我有一个绑定到某些控件的List(T),一个只读的DataGridView,一个ComboBox和一些标签。这样工作正常,当表单加载时,控件都被正确填充,Label.Text和DataGridView行在ComboBox选择被更改时集中所有更改。

但是,如果我更改List上对象中的数据,控件中显示的数据不会更新以反映更改的数据。

我的班级T实现了INotifyChanged接口,标签控件数据绑定更新模式设置为OnPropertychanged

我可以通过调用其Refresh()方法强制DataGridView进行更新,但对标签尝试相同似乎没有任何效果。

那么如何更改列表中对象中的数据更新Label控件中显示的数据?我做错了什么?

到目前为止我的MRE:

Class Form1
    ' Form1 has a DataGridView, a ComboBox, a Label, a Button and a TextBox
    Dim FooList As New List(Of Foo)(3)

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        For Index As Integer = 0 To FooList.Capacity - 1
            FooList.Add(New Foo() With {.Bar = Index, .Baz = 0})
        Next
        ' Shows all Bar and Baz
        DataGridView1.DataSource = FooList
        ' User selects Bar value
        ComboBox1.DataSource = FooList
        ComboBox1.DisplayMember = "Bar" 
        ' Related Baz value shows
        Label1.DataBindings.Add(New Binding("Text", FooList, "Baz", DataSourceUpdateMode.OnPropertyChanged))    
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        ' This is not _actually_ how I'm selecting indexes and changing the data 
        ' But for the MRE it changes the Baz property

        'Change the Baz value on the List, should result in Label1 changing
        FooList(ComboBox1.SelectedItem.Bar).Baz = TextBox1.Text.Convert.ToUInt16 
        ' Should I even need this when my list objects have INotifyChanged?
        DataGridView1.Refresh()
    End Sub
End Class

Class Foo
    Implements INotifyChanged
    Private _bar As UInt16
    Private _baz As UInt16

    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged

    Private Sub NotifyPropertyChanged(ByVal PropertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
    End Sub

    Property Bar As UInt 16
        Get
            Return _bar
        End Get
        Set(value As Byte)
            If Not (value = _bar) Then
                _bar = Bar
                NotifyPropertyChanged("Bar")
            End If
        End Set
    End Property

    Property Baz As UInt 16
        Get
            Return _baz
        End Get
        Set(value As Byte)
            If Not (value = _baz) Then
                _baz = Baz
                NotifyPropertyChanged("Baz")
            End If
        End Set
    End Property
End Class

2 个答案:

答案 0 :(得分:1)

One way to have changes in the collection reflected in bound controls, is to "reset" the DataSource:

FooList.Add(New Foo(...))
dgv1.DataSource = Nothing
dgv1.DataSource = FooList

If the control is something like a ListBox, you have to also reset the DisplayMember and ValueMember properties because they get cleared. This is a greasy way to notify controls of changes to the list because many things get reset. In a ListBox for instance, the SelectedItems collection is cleared.

A much better way to let changes to your collection flow thru to controls is to use a BindingList(Of T). Changes to the collection/list (adds, removes) will automatically and instantly be shown in your control.

INotifyPropertyChanged goes one step further. If a property value on an item in the list changes, the BindingList<T> will catch the PropertyChanged events your class raises and "forward" the changes to the control.

To be clear, the BindingList handlea changes to the list, while INotifyPropertyChanged handles changes to the items in the list.

FooList(13).Name = "Ziggy"

Using a List<T> the name change won't show up unless you "reset" the DataSource. Using a BindingList<T> alone, it wont show up right away - when the list changes, it should show up. Implementing INotifyPropertyChanged allows the change to show up right away.

Your code is mostly correct for it, except it is not INotifyChanged. But there is also a shortcut as of Net 4.5:

Private Sub NotifyChange(<CallerMemberName> Optional propname As String = "")
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
End Sub

CallerMemberName is an attribute which allows you to forego actually passing the name; the named ends up being substituted at runtime:

Private _name As String
Public Property Name As String
    Get
        Return _name
    End Get
    Set(value As String)
        If _name <> value Then
            _name = value
            NotifyChange()
        End If

    End Set
End Property

If nothing else it can cut down on copy/paste errors if there are lots of properties to raise events for (ie Bar property using NotifyChange("Foo") because you copied the code from that setter).

答案 1 :(得分:0)

我想我可以用另一种方式推送数据。也就是说,我不是更新列表中的数据,然后尝试更新控件,而是更新控件,并通过绑定更新列表数据。

E.g。我上面的点击事件现在变为:

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Label1.Text = TextBox1.Text
        DataGridView1.Refresh()
    End Sub

虽然TBH我不是那种粉丝,但我仍然对如何更好地使用INotifyPropertyChanged界面感到困惑。