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?
EDIT
One thing maybe to point is that I'm calling AddBooking
from another ViewModel while BookingListingViewModel
is not at this point visible.
The BookingListingViewModel
never goes out of scope as it's being passed to other ViewModel and it's just being set as CurrentViewModel
after AddBooking finished.
EDIT2 --> Deleted
EDIT3
Rest of the code
CreateBookingViewModel
public ICommand SubmitCreateBookingCommand { get; }
public ICommand CancelCreateBookingCommand { get; }
public ICommand SelectDriverCommand { get; }
public ICommand SelectVehicleCommand { get; }
public ICommand SelectTrailerCommand { get; }
public ICommand SelectHaulierCommand { get; }
/// <summary>
///
/// </summary>
/// <param name="navigationStore"></param>
/// <param name="returnViewModel">View model page should return to</param>
public CreateBookingViewModel(NavigationStore navigationStore, ViewModelBase returnViewModel) // returnViewModel is BookingListingViewModel here
{
WindowStart = DateTime.Now;
WindowEnd = DateTime.Now.AddDays(1);
SubmitCreateBookingCommand = new SubmitNewBookingCommand(navigationStore, this, returnViewModel);
SelectDriverCommand = new NavigateCommand(navigationStore, new DriverListingViewModel(navigationStore, this));
SelectVehicleCommand = new NavigateCommand(navigationStore, new VehicleListingViewModel(navigationStore, this));
SelectTrailerCommand = new NavigateCommand(navigationStore, new TrailerListingViewModel(navigationStore, this));
SelectHaulierCommand = new NavigateCommand(navigationStore, new HaulierListingViewModel(navigationStore, this));
CancelCreateBookingCommand = new NavigateCommand(navigationStore, returnViewModel);
}
SubmitNewBookingCommand
private readonly NavigationStore _navigationStore;
private readonly CreateBookingViewModel _createBookingViewModel;
private readonly ViewModelBase _returnViewModel;
private BookingRecord _validateBookingRecord;
public SubmitNewBookingCommand(NavigationStore navigationStore, CreateBookingViewModel createBookingViewModel, ViewModelBase returnViewModel)
{
this._navigationStore = navigationStore;
this._createBookingViewModel = createBookingViewModel;
this._returnViewModel = returnViewModel;
createBookingViewModel.PropertyChanged += OnViewModelPropertyChanged;
}
public override void Execute(object parameter)
{
BookingRecord bookingRecord = new BookingRecord()
{
CustomerReference = _createBookingViewModel.CustomerReference,
WindowStart = _createBookingViewModel.WindowStart,
WindowEnd = _createBookingViewModel.WindowEnd,
DriverId = _createBookingViewModel.DriverId,
VehicleId = _createBookingViewModel.VehicleId,
TrailerId = _createBookingViewModel.TrailerId,
HaulierId = _createBookingViewModel.HaulierId,
RequiresValidation = true
};
BookingService.CreateBooking(bookingRecord, out string[] feedback);
BookingViewModel booking = BookingService.GetBooking(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
});
((BookingListingViewModel)_returnViewModel).AddBooking(booking);
_navigationStore.CurrentViewModel = _returnViewModel;
}
The behaviour of my original question is still not answered...
Problem in my code was in SubmitNewBookingCommand.Execute
Following code retrieves first found item and not the one I added that's why I didn't see it... My db was populated with dozens of crap data so I didn't see it at first.
BookingViewModel booking = BookingService.GetBooking(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
});
New Code
Booking newBooking = BookingService.CreateBooking(bookingRecord, out string[] feedback);
BookingViewModel booking = BookingService.GetBooking(b => b.BookingId == newBooking.BookingId, 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,
Status = b.BookingStatus.Name
});