我有一个绑定到某些控件的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
答案 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
界面感到困惑。