I am trying to fix a problem with some existing code I am working on where an ObservableCollection is built from a List, but as items are removed from the first list (when it is regenerated from scratch) they are not removed from the ObservableCollection. The list is generated as a part of a DLL and the ObservableCollection is in a part of an exe file and the data is passed between the two by using delegate/event/invoke. The list points to a class which contains an index that can uniquely identify each instance of that class.
The code looks as follows:
DLL
public delegate void EntryListUpdateDelegate(string sender, CarInfo car);
public event EntryListUpdateDelegate OnEntrylistUpdate;
List<CarInfo> _entryListCars = new List<CarInfo>();
case InboundMessageTypes.ENTRY_LIST:
{
_entryListCars.Clear();
var carEntryCount = br.ReadUInt16();
for (int i = 0; i < carEntryCount; i++)
{
_entryListCars.Add(new CarInfo(br.ReadUInt16()));
}
}
case InboundMessageTypes.ENTRY_LIST_CAR:
{
var carId = br.ReadUInt16();
var carInfo = _entryListCars.SingleOrDefault(x => x.CarIndex == carId);
// ... Set attributes for car e.g. carInfo.TeamName = ReadString(br);
OnEntrylistUpdate?.Invoke(ConnectionIdentifier, carInfo);
}
At regular intervals the above code is run to recreate the entryList and keep it up to date.
The CarInfo struct looks like this:
public class CarInfo
{
public ushort CarIndex { get; }
....
}
EXE
The data is passed from the DLL to the EXE using delegate event, the ObservableCollection is created in the ViewModel like this:
public ObservableCollection<CarViewModel> Cars { get; } = new ObservableCollection<CarViewModel>();
internal void RegisterNewClient(MyUdpRemoteClient newClient)
{
if (newClient.MsRealtimeUpdateInterval > 0)
{
// This client will send realtime updates, we should listen
newClient.MessageHandler.OnTrackDataUpdate += MessageHandler_OnTrackDataUpdate;
newClient.MessageHandler.OnEntrylistUpdate += MessageHandler_OnEntrylistUpdate;
newClient.MessageHandler.OnRealtimeUpdate += MessageHandler_OnRealtimeUpdate;
newClient.MessageHandler.OnRealtimeCarUpdate += MessageHandler_OnRealtimeCarUpdate;
}
_clients.Add(newClient.MessageHandler);
}
private void MessageHandler_OnEntrylistUpdate(string sender, CarInfo carUpdate)
{
CarViewModel vm = Cars.SingleOrDefault(x => x.CarIndex == carUpdate.CarIndex);
if (vm == null)
{
vm = new CarViewModel(carUpdate.CarIndex);
Cars.Add(vm);
}
vm.Update(carUpdate);
}
Cars is then used in the xaml file to display the list of Cars in an Entry List in the WPF GUI in a CollectionViewSource.
THE PROBLEM:
_entryListCars in the DLL is always updated and accurate as it is cleared out and reupdated every so often. New cars that come along are successfully added to the Cars ObservableCollection, but Cars that disconnect/die that are no longer a part of _entryListCars are never removed from the Cars ObservableCollection. It just keeps getting bigger and bigger and continues to display the dead/inactive cars.
MY QUESTION:
What is the best way to remove the dead/inactive cars from the Cars ObservableCollection? I wondered whether I would be able to compare the two lists using Except (list1.Except(list2)) and then remove all of the car entries that exist in the Cars ObservableCollection that aren't in the _entryListCars list using maybe Contains on CarIndex and Cars.Remove/RemoveAt/RemoveItem? What is the best way to achieve this and what would the code look like?
Thanks for your help.
Looking at you event handler MessageHandler_OnEntrylistUpdate
the naming would indicate to me that it's expecting a list as the event arguments and not just a new CarInfo
. So to keep that naming, one way you could "fix" this is to change the event to pass the whole list as input instead:
public event EventHandler<List<CarInfo>> OnEntrylistUpdate;
// ...
OnEntrylistUpdate.Invoke(_entryListCars);
Then in your event handler (you can probably do it smarter depending on what you need):
private void MessageHandler_OnEntrylistUpdate(string sender, List<CarInfo> updatedCarList)
{
var newCars = updatedCarList
.Where(uc => !Cars.Any(c => c.CarIndex == uc.CarIndex)
.Select(c => new CarViewModel(c.CarIndex))
.ToList();
var existingCars = Cars
.Where(c => updatedCarList.Any(uc => uc.CarIndex == c.CarIndex)
.ToList();
var removedCars = Cars
.Except(existingCars)
.ToList();
// Do whatever you need with your cars...
foreach (var car in newCars)
Cars.Add(car);
foreach (var car in removedCars)
Cars.Remove(car);
foreach (var car in newCars.Concat(existingCars))
car.Update(updatedCarList.Single(uc => uc.CarIndex == car.CarIndex));
}
All that being said, I would suggest you look into whether you can avoid doing an update of the whole list every time and instead just add/remove/update one car at a time. This requires that your source supports this of course.