DataGrid is Not Updating Properly

RogerSchlueter-7899 1,236 Reputation points
2024-03-10T01:14:34.6233333+00:00

I have a DataGrid that should display a new item when it is added to the ItemsSource but that is not happening and I can't see the error.

The XAML:

<DataGrid
	x:Name="dgTimes"
	ItemsSource="{Binding Path=Walks}">
	<DataGrid.Columns>
	....
</DataGrid>

The ViewModel:

Private CurrentWalk As New Walk

Private _Walks As List(Of Walk)
Public Property Walks As List(Of Walk)
	Get
		Return _Walks
	End Get
	Set(value As List(Of Walk))
		_Walks = value
	End Set
End Property

Private ReadOnly Property _Save As New RelayCommand(Of Integer)(AddressOf DoSave, AddressOf CanSave)
Public ReadOnly Property Save As RelayCommand(Of Integer)
	Get
		Return _Save()
	End Get
End Property

Private Function CanSave(obj As Object) As Boolean
	Return CurrentWalk.IsDirty
End Function

Private Sub DoSave(obj As Object)
	<<Assign all properties to CurrentWalk>>
	CurrentWalk.Add()   'Adds CurrentWalk to a database
	Walks.Add(CurrentWalk)
	RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(Walks)))
End Sub

Shouldn't that RaiseEvent cause the DataGrid to be updated?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,685 questions
VB
VB
An object-oriented programming language developed by Microsoft that is implemented on the .NET Framework. Previously known as Visual Basic .NET.
2,599 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. gekka 6,926 Reputation points MVP
    2024-03-10T12:04:42.24+00:00

    Use ObservableCollection(of T) instead of List(of T).

    ObservableCollection already implements the ability to notify the View of the addition or deletion of elements.


  2. Hui Liu-MSFT 40,866 Reputation points Microsoft Vendor
    2024-03-11T03:05:48.4533333+00:00

    Hi,@ RogerSchlueter-7899. Welcome to Microsoft Q&A .

    You could iterate through the WalksObservableCollection and update the corresponding items in the Walks list.

    I added a method called UpdateWalksList that clears the Walks list and adds each item from the WalksObservableCollection. This method is called whenever the WalksObservableCollection is updated. Now, both Walks and WalksObservableCollection should stay in sync.

    Whenever the data of dgTimes (WalksObservableCollection) is modified, the data of dgTimes1 (Walks) is also updated immediately.

      <Window.DataContext>
    
          <local:ViewModel/>
    
      </Window.DataContext>
    
      <StackPanel>
    
          <DataGrid x:Name="dgTimes" ItemsSource="{Binding WalksObservableCollection}" AutoGenerateColumns="False">
    
              <DataGrid.Columns>
    
                  <DataGridTextColumn Header="ID" Binding="{ Binding  Id,UpdateSourceTrigger=PropertyChanged}"/>
    
                  <DataGridTextColumn Header="Name" Binding="{ Binding Name,UpdateSourceTrigger=PropertyChanged}"/>
    
              </DataGrid.Columns>
    
          </DataGrid>
    
          
    
          <DataGrid x:Name="dgTimes1" ItemsSource="{Binding Walks}" AutoGenerateColumns="False">
    
              <DataGrid.Columns>
    
                  <DataGridTextColumn Header="ID" Binding="{ Binding  Id,UpdateSourceTrigger=PropertyChanged}"/>
    
                  <DataGridTextColumn Header="Name" Binding="{ Binding Name,UpdateSourceTrigger=PropertyChanged}"/>
    
              </DataGrid.Columns>
    
          </DataGrid>
    
      </StackPanel>
    
    

    codebehind:

    
    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class ViewModel
        Implements INotifyPropertyChanged
    
        Public Sub New()
            PopulateData()
            WalksObservableCollection = New ObservableCollection(Of Walk)(Walks)
        End Sub
        Private Sub PopulateData()
            Walks.Add(New Walk() With {
                .Id = 1,
                .Name = "user1"
            })
            Walks.Add(New Walk() With {
                .Id = 2,
                .Name = "user2"
            })
            Walks.Add(New Walk() With {
                .Id = 3,
                .Name = "user3"
            })
            Walks.Add(New Walk() With {
                .Id = 5,
                .Name = "user4"
            })
            Walks.Add(New Walk() With {
                .Id = 6,
                .Name = "user5"
            })
        End Sub
        Private _walksObservableCollection As ObservableCollection(Of Walk)
        Public Property WalksObservableCollection As ObservableCollection(Of Walk)
            Get
                Return _walksObservableCollection
            End Get
            Set(value As ObservableCollection(Of Walk))
                _walksObservableCollection = value
                OnPropertyChanged(NameOf(WalksObservableCollection))
                UpdateWalksList()
            End Set
        End Property
    
        Private _Walks As List(Of Walk) = New List(Of Walk)()
    
        Public Property Walks As List(Of Walk)
            Get
                Return _Walks
            End Get
            Set(value As List(Of Walk))
                _Walks = value
                OnPropertyChanged(NameOf(Walks))
                UpdateObservableCollection()
            End Set
        End Property
    
        Private Sub UpdateObservableCollection()
            WalksObservableCollection.Clear()
            For Each walk In Walks
                WalksObservableCollection.Add(walk)
            Next
        End Sub
    
        Private Sub UpdateWalksList()
            Walks.Clear()
            For Each walk In WalksObservableCollection
                Walks.Add(walk)
            Next
        End Sub
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Private Sub OnPropertyChanged(ByVal propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    End Class
    Public Class Walk
        Implements INotifyPropertyChanged
        Private Property _Id As Integer
        Public Property Id As Integer
            Get
                Return _Id
            End Get
            Set(ByVal value As Integer)
                _Id = value
                OnPropertyChanged(NameOf(Id))
            End Set
        End Property
    
        Private _Name As String
    
        Public Property Name As String
            Get
                Return _Name
            End Get
            Set(ByVal value As String)
                _Name = value
                OnPropertyChanged(NameOf(Name))
            End Set
        End Property
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Private Sub OnPropertyChanged(ByVal propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    End Class
    
    
    

    Update: You could bind the UpdateCommand to the CellEditEnding event using the EventToCommand behavior in XAML.Here is an example using the System.Windows.Interactivity library, which is commonly used with MVVM scenarios.

    Install the System.Windows.Interactivity NuGet package in your project.

    
    <Window x:Class="MainWindow"
    
           ...
            xmlns:local="clr-namespace:DataGridUpdate"  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    
           >
    
        <Window.DataContext>
    
            <local:ViewModel/>
    
        </Window.DataContext>
    
        <StackPanel>
    
            <DataGrid x:Name="dgTimes" ItemsSource="{Binding Walks}"  SelectedItem="{Binding SelectedItem}"  AutoGenerateColumns="False">
    
                <i:Interaction.Triggers>
    
                    <i:EventTrigger EventName="CellEditEnding">
    
                        <i:InvokeCommandAction Command="{Binding EditCellCommand}" 
    
                                               CommandParameter="{Binding SelectedItem, ElementName=dgTimes}" />
    
                    </i:EventTrigger>
    
                </i:Interaction.Triggers>
    
                <DataGrid.Columns>
    
                    <DataGridTextColumn Header="ID" Binding="{ Binding  Id,UpdateSourceTrigger=PropertyChanged}"/>
    
                    <DataGridTextColumn Header="Name" Binding="{ Binding Name,UpdateSourceTrigger=PropertyChanged}"/>
    
                </DataGrid.Columns>
    
            </DataGrid>
    
    

    codebedhind:

    
    Public Class ViewModel
        Implements INotifyPropertyChanged
    
        Public Sub New()
            PopulateData()
           
        End Sub
        Private Sub PopulateData()
            Walks.Add(New Walk() With {
                .Id = 1,
                .Name = "user1"
            })
            Walks.Add(New Walk() With {
                .Id = 2,
                .Name = "user2"
            })
            Walks.Add(New Walk() With {
                .Id = 3,
                .Name = "user3"
            })
            Walks.Add(New Walk() With {
                .Id = 5,
                .Name = "user4"
            })
            Walks.Add(New Walk() With {
                .Id = 6,
                .Name = "user5"
            })
        End Sub
      
    
        Private _Walks As List(Of Walk) = New List(Of Walk)()
    
        Public Property Walks As List(Of Walk)
            Get
                Return _Walks
            End Get
            Set(value As List(Of Walk))
                _Walks = value
                OnPropertyChanged(NameOf(Walks))
            End Set
        End Property
        Private _SelectedItem As Walk
    
        Public Property SelectedItem As Walk
            Get
                Return _SelectedItem
            End Get
            Set(value As Walk)
                _SelectedItem = value
                OnPropertyChanged(NameOf(SelectedItem))
            End Set
        End Property
    
        Private _editCellCommand As ICommand
        Public ReadOnly Property EditCellCommand As ICommand
            Get
                If _editCellCommand Is Nothing Then
                    _editCellCommand = New RelayCommand(AddressOf EditCell)
                End If
                Return _editCellCommand
            End Get
        End Property
    
        ' Implement the logic for updating List(Of Walk)
        Private Sub EditCell(parameter As Object)
            Dim e As DataGridCellEditEndingEventArgs = TryCast(parameter, DataGridCellEditEndingEventArgs)
            Dim editedItem As Walk = TryCast(e.Row.Item, Walk)
    
            If editedItem IsNot Nothing Then
                Dim index As Integer = Walks.IndexOf(editedItem)
    
                If index <> -1 Then
                    Dim editedColumn As DataGridColumn = e.Column
                    Dim propertyName As String = editedColumn.SortMemberPath
                    Dim editedValue As Object = (TryCast(e.EditingElement, TextBox)).Text
    
                    If propertyName = "Id" Then
                        editedItem.Id = Convert.ToInt32(editedValue)
                    ElseIf propertyName = "Name" Then
                        editedItem.Name = editedValue.ToString()
                    End If
    
                    Walks(index) = editedItem
                End If
            End If
        End Sub
      
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Private Sub OnPropertyChanged(ByVal propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    End Class
    Public Class Walk
        Implements INotifyPropertyChanged
        Private Property _Id As Integer
        Public Property Id As Integer
            Get
                Return _Id
            End Get
            Set(ByVal value As Integer)
                _Id = value
                OnPropertyChanged(NameOf(Id))
            End Set
        End Property
    
        Private _Name As String
    
        Public Property Name As String
            Get
                Return _Name
            End Get
            Set(ByVal value As String)
                _Name = value
                OnPropertyChanged(NameOf(Name))
            End Set
        End Property
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Private Sub OnPropertyChanged(ByVal propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    End Class
    
    Public Class RelayCommand
        Implements ICommand
    
        Private ReadOnly _execute As Action(Of Object)
        Private ReadOnly _canExecute As Func(Of Object, Boolean)
    
        Public Sub New(execute As Action(Of Object), Optional canExecute As Func(Of Object, Boolean) = Nothing)
            _execute = execute
            _canExecute = canExecute
        End Sub
    
        Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
            AddHandler(ByVal value As EventHandler)
                If _canExecute IsNot Nothing Then
                    AddHandler CommandManager.RequerySuggested, value
                End If
            End AddHandler
    
            RemoveHandler(ByVal value As EventHandler)
                If _canExecute IsNot Nothing Then
                    RemoveHandler CommandManager.RequerySuggested, value
                End If
            End RemoveHandler
    
            RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
                If _canExecute IsNot Nothing Then
                    CommandManager.InvalidateRequerySuggested()
                End If
            End RaiseEvent
        End Event
    
        Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
            If _canExecute Is Nothing Then
                Return True
            Else
                Return _canExecute(parameter)
            End If
        End Function
    
        Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
            _execute(parameter)
        End Sub
    End Class
    
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.