How do I get INotifyPropertyChanged to update boun

2020-05-03 11:04发布

问题:

I am still learning how to use INotifyPropertyChanged. I was able to see it work with a non-persisted list in this post. I am now trying to use an SQL persisted list.

XAML. Some of the unused grid space was for comparisons to the non-persisted list, but not relevant to this post's question.

<StackPanel Grid.Column="0">
  <Button Margin="25" Content="Insert an item" Click="InsertAnItem_Click"/>
</StackPanel>

<StackPanel Grid.Column="1">
  <Button Margin="25" Content="Clear stored list" Click="ClearStoredList_Click"/>

  <Label Content="{}{Binding MyList2}"/>
  <ListBox Name="listBox3" ItemsSource="{Binding MyList2}" DisplayMemberPath="Name" Height="100">
  </ListBox>

  <Label Content="updated with MyList2 using code behind"/>
  <ListBox Name="listBox4" DisplayMemberPath="Name" Height="100">
  </ListBox>
</StackPanel>

CodeBehind

Partial Class MainWindow

  Private vm = New ViewModel

  Sub New()
    InitializeComponent()
    DataContext = vm
    For Each item In vm.MyList2
      listBox4.Items.Add(item)
    Next
  End Sub

  Private Sub InsertAnItem_Click(sender As Object, e As RoutedEventArgs)
    vm.InsertAnItem()
    listBox4.Items.Clear()
    For Each item In vm.MyList2
      listBox4.Items.Add(item)
    Next
  End Sub

  Private Sub ClearStoredList_Click(sender As Object, e As RoutedEventArgs)
    vm.ClearStoredList()
  End Sub
End Class

ViewModel

Public Class ViewModel
  Implements INotifyPropertyChanged

  Public Property dbList As New Mvvm2DbContext
  Public Property MyList2 As New List(Of MyListItem)

  Public Sub New()
    MyList2 = dbList.SqlStoredList.ToList()
  End Sub

  Public Sub InsertAnItem()
    Dim anItem As New MyListItem
    anItem.Name = "Item " & MyList2.Count()
    MyList2.Add(anItem)
    dbList.SqlStoredList.Add(anItem)
    dbList.SaveChanges()
    NotifyPropertyChanged("MyList2")
  End Sub

  Public Sub ClearStoredList()
    Using db = New Mvvm2DbContext
      Dim allItems = db.SqlStoredList
      Try
        db.SqlStoredList.RemoveRange(allItems)
      Catch ex As Exception
        Dim s = ex.Message
        MessageBox.Show(s)
      End Try
      db.SaveChanges()
    End Using
  End Sub

  Public Event PropertyChanged As PropertyChangedEventHandler _
    Implements INotifyPropertyChanged.PropertyChanged

  Public Sub NotifyPropertyChanged(ByVal propertyName As String)
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
  End Sub
End Class

Model

Public Class MyListItem

  Public Property MyListItemID() As Integer
    Get
      Return _MyListItemID
    End Get
    Set(ByVal value As Integer)
      _MyListItemID = value
    End Set
  End Property
  Private _MyListItemID As Integer

  Public Property Name() As String
    Get
      Return _Name
    End Get
    Set(ByVal value As String)
      _Name = value
    End Set
  End Property
  Private _Name As String

  Public Property Desc() As String
    Get
      Return _Desc
    End Get
    Set(ByVal value As String)
      _Desc = value
    End Set
  End Property
  Private _Desc As String
End Class

dbContext

Public Class Mvvm2DbContext
  Inherits DbContext

  Public Sub New()
    MyBase.New("name=MVVM2DbConnection")
  End Sub

  Public Property SqlStoredList As DbSet(Of MyListItem)
End Class

My line NotifyPropertyChanged("MyList2") in the ViewModel doesn't cause the bound list to update.

I think the MyList2 definition in the ViewModel must be incorrect, but I don't understand how to make it different if that's the problem.

What is the proper code to get changes to percolate to the display? As always, if there are best practices I'm missing, please advise.

Updated after initial post but questions still remain:

The following changes appear to cause INotifyPropertyChanged to now work in this code. I'm guessing INotifyPropertyChanged doesn't work unless a property is defined with specific get/set declarations.

ViewModel. The MyList2 definition is changed and New() is changed.

Public Property MyList2() As ObservableCollection(Of MyListItem)
Get
  Return _MyList2
End Get
Set(ByVal value As ObservableCollection(Of MyListItem))
  _MyList2 = value
End Set
End Property
Private _MyList2 As New ObservableCollection(Of MyListItem)

Public Sub New()
  For Each item In dbList.SqlStoredList
    MyList2.Add(item)
  Next
End Sub

The reason I previously defined MyList2 as:

Public Property MyList2 As New List(Of MyListItem)

Was because in New() initializing MyList2 with this line wouldn't compile:

MyList2 = dbList.SqlStoredList.ToList()

Even if I tried a DirectCast, List(Of T) can't be converted to an ObservableCollection(Of T). I'm guessing that's because of all the 'plumbing' behind the scenes?

Anyway, INotifyPropertyChanged seems to work fine now. But, I would appreciate any instruction regarding mistakes I'm making implementing this simple persisted list with INotifyPropertyChanged.

However, the persisted list is not fully bound. MyList2 doesn't clear when the SQL database is cleared. It seems to me that there must be more to this because changes to the SQL database could be made by other processes, and I don't see how this code would know that.

What do I do to ensure all updates get reflected in the UI?

回答1:

The problem with your code is that the property MyList2 is not changing...its contents are changing. So that is why the property notification on MyList2 does nothing...MyList2 is still MyList2.

What you need is a collection class that implements notification. ObservableCollection would fit this need. So if you simply replace List(Of MyListItem) with ObservableCollection(Of MyListItem) I think things will start working.