【问题标题】:Update data bound control when data changes数据更改时更新数据绑定控件
【发布时间】:2015-08-14 10:29:36
【问题描述】:

我有一个绑定到一些控件的List(T)、一个只读 DataGridView、一个 ComboBox 和一些标签。这很好用,当表单加载时,控件都正确填充,Label.Text 和 DataGridView 行焦点都随着 ComboBox 选择的变化而变化。

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

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

我可以通过调用它的Refresh() 方法来强制DataGridView 进行更新,但是对标签尝试同样的方法似乎没有效果。

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

到目前为止我的 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

【问题讨论】:

    标签: vb.net data-binding


    【解决方案1】:

    将集合中的更改反映在绑定控件中的一种方法是“重置”DataSource

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

    如果控件类似于ListBox,您还必须重置DisplayMemberValueMember 属性,因为它们会被清除。这是一种通知控件列表更改的简便方法,因为许多事情都会被重置。例如,在 ListBox 中,SelectedItems 集合被清除。

    让对集合的更改流向控件的更好方法是使用BindingList(Of T)集合/列表的更改(添加、删除)将自动并立即显示在您的控件中。

    INotifyPropertyChanged 更进一步。如果列表中某项的属性值发生更改,BindingList<T> 将捕获您的类引发的PropertyChanged 事件并将更改“转发”到控件。

    需要明确的是,BindingList 句柄更改为 列表,而INotifyPropertyChanged 处理列表中项目的更改。

    FooList(13).Name = "Ziggy"
    

    使用List<T> 不会显示名称更改,除非您“重置”DataSource。单独使用BindingList<T>,它不会立即显示 - 当列表更改时,它应该显示。实施INotifyPropertyChanged 可以立即显示更改。

    你的代码大部分是正确的,除了它不是INotifyChanged。但是从 Net 4.5 开始也有一个捷径:

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

    CallerMemberName 是一个允许您放弃实际传递名称的属性;命名的最终在运行时被替换:

    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
    

    如果有很多属性要引发事件,它可以减少复制/粘贴错误(例如,使用 NotifyChange("Foo") 的 Bar 属性,因为您从该 setter 复制了代码)。

    【讨论】:

    • 谢谢,使用 BindingList 是可行的,尽管我确实必须创建自己的继承 BindingList 的类,以便我可以添加一些特定于 List 的方法来减少当前代码库中的更改。 WRT CallerMemberName 事实证明,虽然我安装了 .Net 4.5,但 VS2010 最多只能使用 v4.0。我仍然觉得语言更新与 IDE 而不是仅仅与框架版本相关联是很愚蠢的。 Grr.
    【解决方案2】:

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

    例如我的点击事件现在变成了:

        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 界面感到困惑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多