Home > Back-end >  How to update ListBox with ObservableCollection WPF MVVC?
How to update ListBox with ObservableCollection WPF MVVC?

Time:06-24

I'm learning WPF MVVM pattern. I've been following Sean Singleton tutorial on YT and so far I've understood and managed to apply the patter to my application.

However there is one aspect that I don't understand what's happening...

I have a view with ListBox that has Binding to ObservableCollection on ViewModel. ViewModel inherits from ViewModelBase which in turn introduced 'NotifyPropertyChanged.

Sean did some method to update the collection by clearing it first and then fetching all values again... Surely as ObservableCollection I should be able to add an item and it should notify the UI that something has changed?

So what I have so far(unnecessary code was removed for better readibility)

View

<Grid Grid.Row="1" Background="#FFE5E5E5" Margin="0,0,0,0">
        <ListView Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="10 10"  BorderBrush="{StaticResource DHLYellow}" BorderThickness="1" ItemsSource="{Binding Bookings}">

ViewModelBase

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GIO.UI.ViewModels
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

BookingListingViewModel

using GIO.Interfaces;
using GIO.Services;
using GIO.UI.Commands;
using GIO.UI.Stores;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;


namespace GIO.UI.ViewModels
{
    public class BookingListingViewModel : ViewModelBase
    {

        private ObservableCollection<BookingViewModel> _bookings;

        private readonly NavigationStore _navigationStore;

        public ViewModelBase CurrentViewModel => _navigationStore.CurrentViewModel;

        public IEnumerable<BookingViewModel> Bookings => _bookings;

        public ICommand CreateBookingCommand { get; }

        public BookingListingViewModel(NavigationStore navigationStore)
        {
            _bookings = new ObservableCollection<BookingViewModel>();

            var bookings = BookingService.GetBookings(b => true, b => new BookingViewModel()
            {
                CustomerReference = b.CustomerRef,
                Status = b.BookingStatus.Name,
                WindowStart = b.BookingWindowFrom,
                WindowEnd = b.BookingWindowTo,
                DriverName = b.Driver.Name,
                TrailerName = b.Trailer.Name,
                VehicleRegPlate = b.Vehicle.RegPlate,
                HaulierName = b.Haulier.Name

            });

            foreach( BookingViewModel b in bookings)
            {
                _bookings.Add(b);
            }

            CreateBookingCommand = new NavigateCommand(navigationStore, new CreateBookingViewModel(navigationStore, this));
            _navigationStore = navigationStore;
        }

        public void AddBooking(BookingViewModel booking) //Command would call this method
        {
            _bookings.Add(booking); //Doesn't update the ListBox on UI
            OnPropertyChanged(nameof(Bookings)); // Not in Sean's tutorial. I added that code to try to trigger the notify but it doesn't work

            //UpdateBookings(); //Sean's code. It works but it doesn't do anything special other than clear collection and add items. 
        }

        private void UpdateBookings()
        {
            _bookings.Clear();

            foreach (BookingViewModel b in BookingService.GetBookings(b => true, b => new BookingViewModel()
            {
                CustomerReference = b.CustomerRef,
                WindowStart = b.BookingWindowFrom,
                WindowEnd = b.BookingWindowTo,
                DriverName = b.Driver.Name,
                VehicleRegPlate = b.Vehicle.RegPlate,
                TrailerName = b.Trailer.Name,
                HaulierName = b.Haulier.Name
            }))
            {
                _bookings.Add(b);
            }
        }
    }
}

So I'm baffled as to why my ListBox is not updating... What can I do to make it update so I don't need to clear collection and update again?

Also why simple _bookings.Add(booking); doesn't update the ListBox but

_bookings.Clear();
_bookings.Add(b); 

does?

CodePudding user response:

I think the problem is that you're exposing the Bookings property as an IEnumerable<T> instead of ObservableCollection<T>. That might be the cause of the UI not updating when you add items.

Here's a tweaked version of your view model.

using GIO.Interfaces;
using GIO.Services;
using GIO.UI.Commands;
using GIO.UI.Stores;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace GIO.UI.ViewModels
{
    public class BookingListingViewModel : ViewModelBase
    {
        public ObservableCollection<BookingViewModel> Bookings { get; } = new ObservableCollection<BookingViewModel>();

        private readonly NavigationStore _navigationStore;

        public ViewModelBase CurrentViewModel => _navigationStore.CurrentViewModel;

        public ICommand CreateBookingCommand { get; }

        public BookingListingViewModel(NavigationStore navigationStore)
        {
            var bookings = BookingService.GetBookings(b => true, b => new BookingViewModel()
            {
                CustomerReference = b.CustomerRef,
                Status = b.BookingStatus.Name,
                WindowStart = b.BookingWindowFrom,
                WindowEnd = b.BookingWindowTo,
                DriverName = b.Driver.Name,
                TrailerName = b.Trailer.Name,
                VehicleRegPlate = b.Vehicle.RegPlate,
                HaulierName = b.Haulier.Name
            });

            foreach( BookingViewModel b in bookings)
            {
                Bookings.Add(b);
            }

            CreateBookingCommand = new NavigateCommand(navigationStore, new CreateBookingViewModel(navigationStore, this));
            _navigationStore = navigationStore;
        }

        public void AddBooking(BookingViewModel booking) //Command would call this method
        {
            Bookings.Add(booking);
        }

        private void UpdateBookings()
        {
            Bookings.Clear();

            foreach (BookingViewModel b in BookingService.GetBookings(b => true, b => new BookingViewModel()
            {
                CustomerReference = b.CustomerRef,
                WindowStart = b.BookingWindowFrom,
                WindowEnd = b.BookingWindowTo,
                DriverName = b.Driver.Name,
                VehicleRegPlate = b.Vehicle.RegPlate,
                TrailerName = b.Trailer.Name,
                HaulierName = b.Haulier.Name
            }))
            {
                Bookings.Add(b);
            }
        }
    }
}

CodePudding user response:

I had the same issue. Try to replase Bookings property type to ReadOnlyObservableCollection<BookingViewModel>.

public ReadOnlyObservableCollection<BookingViewModel> Bookings { get; }

public BookingListingViewModel(NavigationStore navigationStore)
{
    _bookings = new ObservableCollection<BookingViewModel>();
    Bookings = new ReadOnlyObservableCollection<BookingViewModel>(_bookings);
// other ctor actions
}
  • Related