【问题标题】:Remove from Observable collection in ViewModel does not update View, but Updates of Existing Items does update View从 ViewModel 中的 Observable 集合中移除不会更新 View,但 Updates of Existing Items 会更新 View
【发布时间】:2013-09-05 22:26:00
【问题描述】:

我相信这对某人来说将是一次扣篮...... 手指交叉

我的 ListView ItemsSource 绑定到我的 ViewModel 上名为 TileItems 的属性。

完美地填充列表视图。

在 ViewModel 中,您会看到 "existingTileItem.Transaction = e.Transaction" 。 . .单个列表视图项目完美更新。

在 ViewModel 中,您会看到“Me.TileItems.Remove(existingTileItem)”...该项目不会从视图中删除。它确实从 Me.TileItems 集合中成功删除,但视图中未显示更新。

附加信息:AbstractViewModel 实现了 INotificationPropertyChanged,我已经尝试在 TileItem 中覆盖 Equals 而不是覆盖它,并且周围都发生了相同的结果。我看过this 的回答和this 的回答,但他们没有回答我遇到的问题。

XAML:

<UserControl.DataContext>
    <local:TransactionTileResultsViewControlViewModel />
</UserControl.DataContext>

<ListView Grid.Row="1"  Name="tileItems" ItemsSource="{Binding TileItems, Mode=TwoWay}" 
                  ItemTemplate="{StaticResource tileItemDataTemplate}" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
                  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

视图模型:

Public Class TransactionTileResultsViewControlViewModel
    Inherits AbstractViewModel
    Implements INavigationAware

    Private _tileItems As TileItems
    Public Property TileItems As TileItems
        Get
            Return Me._tileItems
        End Get
        Set(value As TileItems)
            Me._tileItems = value
            MyBase.RaisePropertyChanged("TileItems")
        End Set
    End Property

'....


    #Region "TransactionUpdateReceived Methods"

        Private Sub TransactionUpdateReceived_Handler(ByVal e As TransactionUpdatedEvent)

            If e.Transaction IsNot Nothing Then

                Dim existingTileItem As TileItem = Me.TileItems.Where(Function(t) t.Transaction.TransactionQueueID = e.Transaction.TransactionQueueID).FirstOrDefault()
                If existingTileItem IsNot Nothing Then

                    If e.Transaction.Canceled Then

                          Me.TileItems.Remove(existingTileItem)

                    Else

                        If e.Transaction.ContainsFailedActivites() OrElse e.Transaction.ContainsCallbackActivities() Then

                            existingTileItem.Transaction = e.Transaction

                        Else

                            Me.TileItems.Remove(existingTileItem)

                        End If

                    End If

                End If

            End If

        End Sub

#End Region

End Class

TileItems 模型:

Public Class TileItems
    Inherits ObservableCollection(Of TileItem)

End Class

TileItem 模型:

 Imports Microsoft.Practices.Prism.ViewModel

    Public Class TileItem
        Inherits NotificationObject

        Private _created As Date
        Public Property Created As Date
            Get
                Return _created
            End Get
            Set(value As Date)
                _created = value
                MyBase.RaisePropertyChanged("Created")
            End Set
        End Property

        Private _category As String
        Public Property Category As String
            Get
                Return _category
            End Get
            Set(value As String)
                _category = value
                MyBase.RaisePropertyChanged("Category")
            End Set
        End Property

        Private _tileField1 As String
        Public Property TileField1 As String
            Get
                Return _tileField1
            End Get
            Set(value As String)
                _tileField1 = value
                MyBase.RaisePropertyChanged("TileField1")
            End Set
        End Property

        Private _tileField2 As String
        Public Property TileField2 As String
            Get
                Return _tileField2
            End Get
            Set(value As String)
                _tileField2 = value
                MyBase.RaisePropertyChanged("TileField2")
            End Set
        End Property

        Private _tileField3 As String
        Public Property TileField3 As String
            Get
                Return _tileField3
            End Get
            Set(value As String)
                _tileField3 = value
                MyBase.RaisePropertyChanged("TileField3")
            End Set
        End Property

        Private _transaction As Transaction
        Public Property Transaction As Transaction
            Get
                Return _transaction
            End Get
            Set(value As Transaction)
                _transaction = value
                MyBase.RaisePropertyChanged("Transaction")
            End Set
        End Property

    Public Overrides Function Equals(obj As Object) As Boolean

        If TypeOf obj Is TileItem Then

            Dim tileItem As TileItem = DirectCast(obj, TileItem)

            If tileItem.Transaction IsNot Nothing AndAlso Me.Transaction IsNot Nothing Then

                Return tileItem.Transaction.TransactionQueueID = Me.Transaction.TransactionQueueID

            Else
                Return False

            End If

        Else
            Return False
        End If

    End Function


    End Class

更新:

根据@ReedCopsey 的回答,这是我为使其正常工作而进行的更新。

我现在将 Me.TileItems.Remove(existingTileItem) 更新为这个

Me.View.Dispatcher.Invoke(Sub()
                              Me.TileItems.Remove(existingTileItem)
                          End Sub, DispatcherPriority.ApplicationIdle)

【问题讨论】:

  • 为什么要为TileItems 创建自定义类?
  • 在我不久前参加的 Billy Hollis 课程中,他说在使用泛型集合时要这样做......所以这只是习惯,在这种确切情况下可能不需要。我试过不这样做,也就是只使用 ObservableCollection(Of TileItem) 作为 TileItems 属性类型,结果相同。
  • 总的来说,我实际上认为这是一种不好的做法 - 除非您添加额外的行为,否则无需创建额外的类来维护和测试。
  • @ReedCopsey ...嗯,好的。不确定这对我的问题有什么帮助,但感谢您的观察。记下:)
  • 一般来说,除非您明确向其添加自定义逻辑,否则无需将集合包装到自定义类中。

标签: .net wpf mvvm viewmodel observablecollection


【解决方案1】:

据我所知,您粘贴的代码应该可以工作。最可能的罪魁祸首是您的TransactionUpdateReceived 事件是在不是用户界面线程的线程上引发的。在 WPF 中,单个项目可以在后台线程上修改,但集合不能(在 .NET 4.5 之前,但在 .NET 4.5 中,它们需要额外的工作)。

有两种选择。如果您使用的是 .NET 4.5,则可以使用 BindingOperations.EnableCollectionSynchronization 允许从后台线程修改 ObservableCollection

或者,您可以使用Dispatcher.Invoke 将添加/删除调用推送到主线程。

【讨论】:

  • 您是正确的,因为该事件是通过订阅的服务事件引发的,因此它在另一个线程上。我们还没有在我们的 UI 中使用 4.5,但是当我们这样做时,我肯定会接受这个建议。我现在正在尝试 Dispatch.Invoke ... 请注意,我已经尝试过: Me.View.Dispatcher.BeginInvoke(Sub() Me.TileItems.Remove(existingTileItem) End Sub, DispatcherPriority.ApplicationIdle) ... 但没有工作......但这也是异步的,所以它不起作用是有道理的:)
  • @wakurth 是的,您需要使用 Dispatcher.Invoke,而不是 begininvoke,或者重新编写调用中的逻辑,以便在最后调用所有操作。
  • Dispatcher.Invoke 成功了……非常感谢先生。已接受答案!
  • 您能否详细说明如何在代码中使用 BindingOperations.EnableCollectionSynchronization?文档令人困惑。什么是锁对象?
  • @HendyIrawan 您创建了一个单独的对象,每当您更改集合时都会锁定该对象,WPF 将根据需要锁定,因此它可以同步。通常只是object lockOnMe = new object();
猜你喜欢
  • 1970-01-01
  • 2020-12-17
  • 2021-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-11
  • 1970-01-01
相关资源
最近更新 更多