在项目 属性 更改时更新 ObservableCollection

Update ObservableCollection on item property change

我正在尝试让 ObservableCollection 在更改项目的 属性 时触发 CollectionChanged 事件。我使用了 here 中的代码来完成它,但也许我在从 C# 转换为 Vb.net 时出错了。无论如何 item_PropertyChanged 都没有开火。我在这里错过了什么?

代码:

Imports System.ComponentModel
Imports System.Collections.Specialized
Imports System.Collections.ObjectModel

Class MainWindow

    Public Class TrulyObservableCollection(Of T As INotifyPropertyChanged)
        Inherits ObservableCollection(Of T)
        Public Sub New()
            MyBase.New()
            AddHandler CollectionChanged, AddressOf TrulyObservableCollection_CollectionChanged
        End Sub

        Private Sub TrulyObservableCollection_CollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
            If e.NewItems IsNot Nothing Then
                For Each item As [Object] In e.NewItems
                    AddHandler TryCast(item, INotifyPropertyChanged).PropertyChanged, AddressOf item_PropertyChanged
                Next
            End If
            If e.OldItems IsNot Nothing Then
                For Each item As [Object] In e.OldItems
                    RemoveHandler TryCast(item, INotifyPropertyChanged).PropertyChanged, AddressOf item_PropertyChanged
                Next
            End If
        End Sub

        Private Sub item_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
            Dim a As New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)
            OnCollectionChanged(a)
        End Sub
    End Class

    Public Class edm
        Implements INotifyPropertyChanged
        Property ip As String
        Property status As String
        Public Sub New(ip As String, status As String)
            Me.ip = ip
            Me.status = status
        End Sub

        Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
    End Class

    Public Property edms As New TrulyObservableCollection(Of edm)

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        DataContext = Me
        edms.Add(New edm("192.168.1.111", "On"))
        edms.Add(New edm("192.168.1.112", "Off"))
        edms.Add(New edm("192.168.1.113", "On"))

    End Sub

    Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
        edms.Where(Function(edm) edm.ip = "192.168.1.111").First().status = "Off"
    End Sub
End Class

编辑:

Bjørn 和 Liero 都提供了很好的答案,在这种情况下我发现很难将一个标记为正确而不是另一个所以我选择的理由是 Bjørn 的 post 回答了我提出的问题它,我标记了 Liero 的回答,因为他的评论引导我找到适合我的场景的最佳解决方案。

如果值与支持字段不同,您需要在每个 属性 的 setter 中引发 属性 更改事件。

Public Property Foo() As String
    Get
        Return Me.m_foo
    End Get
    Set(value As String)
        If (value <> Me.m_foo) Then
            Me.m_foo = value
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Foo"))
        End If
    End Set
End Property

所以你的 edm class 应该看起来更像这样:

Public Class Edm
    Implements INotifyPropertyChanged

    Public Sub New(ip As String, status As String)
        Me.m_ip = ip
        Me.m_status = status
    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Property Ip() As String
        Get
            Return Me.m_ip
        End Get
        Set(value As String)
            If (value <> Me.m_ip) Then
                Me.m_ip = value
                Me.NotifyPropertyChanged("Ip")
            End If
        End Set
    End Property

    Public Property Status() As String
        Get
            Return Me.m_status
        End Get
        Set(value As String)
            If (value <> Me.m_status) Then
                Me.m_status = value
                Me.NotifyPropertyChanged("Status")
            End If
        End Set
    End Property

    Private Sub NotifyPropertyChanged(propertyName As String)
        Me.OnPropertyChanged(New PropertyChangedEventArgs(propertyName))
    End Sub

    Protected Overridable Sub OnPropertyChanged(e As PropertyChangedEventArgs)
        RaiseEvent PropertyChanged(Me, e)
    End Sub

    Private m_ip As String
    Private m_status As String

End Class

而且,正如 liero in his/her 正确指出的那样,您最好覆盖 InsertItemSetItemRemoveItemClearItems 而不是处理 CollectionChanged事件。

Public Class TrulyObservableCollection(Of T As INotifyPropertyChanged)
    Inherits ObservableCollection(Of T)

    Protected Overrides Sub InsertItem(index As Integer, item As T)
        MyBase.InsertItem(index, item)
        Me.HookItem(item)
    End Sub

    Protected Overrides Sub SetItem(index As Integer, newItem As T)
        Dim oldItem As T = Me.Items(index)
        MyBase.SetItem(index, newItem)
        Me.UnhookItem(oldItem)
        Me.HookItem(newItem)
    End Sub

    Protected Overrides Sub RemoveItem(index As Integer)
        Dim item As T = Me.Items(index)
        MyBase.RemoveItem(index)
        Me.UnhookItem(item)
    End Sub

    Protected Overrides Sub ClearItems()
        For Each item As T In Me.Items
            Me.UnhookItem(item)
        Next
        MyBase.ClearItems()
    End Sub

    Private Sub HookItem(item As T)
        If (Not item Is Nothing) Then AddHandler item.PropertyChanged, AddressOf Me.HandleItemPropertyChanged
    End Sub

    Private Sub UnhookItem(item As T)
        If (Not item Is Nothing) Then RemoveHandler item.PropertyChanged, AddressOf Me.HandleItemPropertyChanged
    End Sub

    Private Sub HandleItemPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
        Me.OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
    End Sub

End Class

你是对的,你需要附加到每个项目的 PropertyChanged 事件。但是您不能在订阅时依赖 CollectionChanged 事件。例如,当您清除集合时,旧项目不在事件参数中。项目也可以传递给 ObservableCollection ctor。

更好的方法是覆盖方法 ClearItemsRemoveItemInsertItemSetItem

已经有一些实现,例如:

当然我前段时间自己写过:) 但是自从 WPF 引入了 live shaping 我真的不需要它:

编辑: 不要忘记在 属性 更改时触发 属性changed 事件,就像@Bjørn-Roger Kringsjå 提议的那样。根据评论,您的问题不在 observablecollection 中,而是 class

的 INotifyPropertyChanged 实现