Nested ObservableCollection - Propogate notification from child to parent
c#, mvvm, mvvm-light, wpf
Solution
I'm guessing that the `ObservableObject` class is your own implementation of `INotifyPropertyChanged` interface. Now to solve your issues:
You should register to `CollectionChanged` event in `_punchDetailModels` and raise a `PropertyChanged` event for that variable in the handler, like so:
public ObservableCollection<PunchDetailModel> PunchDetailModels
{
get
{
return _punchDetailModels;
}
set
{
Set(PunchDetailModelsPropertyName, ref _punchDetailModels, value);
_punchDetailModels.CollectionChanged += handler;
}
}
private void handler(object sender, NotifyCollectionChangedEventArgs e)
{
base.RaisePropertyChanged(PunchDetailModelsPropertyName); // If you don't have a method with such signature in ObservableObject (one that takes a string and raises PropertyChanged for it) you'll have to write it.
}
This way the view should reload automatically when adding or removing elements from the inner collection.
There is no other way than to subscribe to listen to `PropertyChanged` on these fields. That's what the `View` does and that's what the `ViewModel` should do also. Like so:
public const string AttendanceCollectionPropertyName = "AttendanceCollection";
private ObservableCollection<AttendanceModel> _attendanceCollection = null;
public ObservableCollection<AttendanceModel> AttendanceCollection
{
get
{
if (_attendanceCollection == null)
{
_attendanceCollection = new ObservableCollection<AttendanceModel>();
}
return _attendanceCollection;
}
set
{
Set(AttendanceCollectionPropertyName, ref _attendanceCollection, value);
_attendanceCollection.CollectionChanged+= handler
}
}
private void handler(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (AttendanceModel model in AttendanceCollection)
model.PropertyChanged += somethingChanged;
}
// Very ineffective to subscribe to all elements every time a list changes but I leave optimization to you.
private somethingChanged (object obj, PropertyChangedEventArgs args)
{
if ( args.PropertyName == "CheckIn" ) // for example
{
AttendanceModel ModelToRecalculate = obj as AttendanceModel;
// You can do anything you want on that model.
}
}
And of course you need to raise `PropertyChanged` with `string` argument of value `CheckIn` in the `AttendanceModel` class when You think it's necessary ( for example in the `handler` method)
EDIT:
To answer your comment question:
"Come to second one - I need to recalculate the Attendance Model properties like InOutCount, TotalInTime, TotalOutTime on PunchTime field update."
The answer is: You don't need to do anything in the `ViewModel` to "recalculate". The UI is subscribed to `PropertyChange`for `InOutCount` , `FirstCheckIn` ... and so on. It's beacause of `Binding` (it does it automatically).
So All you need to do to inform the UI that given model needs to be recalculated is call `RaisePropertyChanged("InOutCount")`, `RaisePropertyChanged("FirstCheckIn")`. The UI will understand that it needs to GET these properties and because you have these calcualations in property getters, it'll get recalculated.
So, I see that UI needs to be recalculated every time that the INNER list changes, so all you need to do is change the `handler` code to `CollectionChanged` for `PunchDetailModels` like this:
// the handler for CollectionChanged for the INNER collection (PunchDetailModels)
private void handler(object sender, NotifyCollectionChangedEventArgs e)
{
base.RaisePropertyChanged(PunchDetailModelsPropertyName); // If you don't have a method with such signature in ObservableObject (one that takes a string and raises PropertyChanged for it) you'll have to write it.
base.RaisePropertyChanged("InOutCount")
base.RaisePropertyChanged("FirstCheckIn")
base.RaisePropertyChanged("LastCheckOut")
// and so on for all the properties that need to be refreshed
}
Problem
I'm developing a WPF application with MVVM Light Toolkit. I just want to display a nested `Observablecollection` which hold the Employee Attendance details into a `DataGrid` and do some CRUD functionality in the inner grid and based on those changes I have to automatically recalculate the Outer collection record. The inner collection (`PunchDetailModels`) is showing in the `RowDetailsTemplate` of the `DataGrid`. Here is the Models : ``` public class AttendanceModel : ObservableObject { public const string EmpNamePropertyName = "EmpName"; private string _empName = string.Empty; public string EmpName { get { return _empName; } set { Set(EmpNamePropertyName, ref _empName, value); } } public const string PunchDetailModelsPropertyName = "PunchDetailModels"; private ObservableCollection<PunchDetailModel> _punchDetailModels = null; public ObservableCollection<PunchDetailModel> PunchDetailModels { get { return _punchDetailModels; } set { Set(PunchDetailModelsPropertyName, ref _punchDetailModels, value); } } private string _inOutCount; public string InOutCount { get { return PunchDetailModels != null ? string.Format("{0}/{1}", PunchDetailModels.Count(i => i.PunchStatus == Enums.PunchType.CheckIn), PunchDetailModels.Count(i => i.PunchStatus == Enums.PunchType.CheckOut)) : null; } } public TimeSpan? FirstCheckIn { get { if (_punchDetailModels != null) { var firstCheckIn = _punchDetailModels.OrderBy(t => t.PunchTime) .FirstOrDefault(i => i.PunchStatus == Enums.PunchType.CheckIn); if (firstCheckIn != null) return firstCheckIn.PunchTime; } return null; } } public TimeSpan? LastCheckOut { get { if (_punchDetailModels != null) { var lastCheckOut = _punchDetailModels.OrderBy(t => t.PunchTime) .LastOrDefault(o => o.PunchStatus == Enums.PunchType.CheckOut); if (lastCheckOut != null) return lastCheckOut.PunchTime; } return null; } } public TimeSpan? TotalInTime { get { TimeSpan totalInTime = TimeSpan.Zero; if (_punchDetailModels != null) { if (!IsValidRecord()) return null; for (int inTime = 0; inTime < _punchDetailModels.Count; inTime += 2) { totalInTime += _punchDetailModels[inTime + 1].PunchTime - _punchDetailModels[inTime].PunchTime; } } return totalInTime; } } public TimeSpan? TotalOutTime { get { TimeSpan totalInTime = TimeSpan.Zero; if (_punchDetailModels != null) { if (!IsValidRecord()) return null; for (int inTime = 1; inTime < _punchDetailModels.Count - 1; inTime += 2) { totalInTime += _punchDetailModels[inTime + 1].PunchTime - _punchDetailModels[inTime].PunchTime; } } return totalInTime; } } } public class PunchDetailModel : ObservableObject { public const string PunchStatusPropertyName = "PunchStatus"; private Enums.PunchType _punchStatus; public Enums.PunchType PunchStatus { get { return _punchStatus; } set { Set(PunchStatusPropertyName, ref _punchStatus, value); } } public const string PunchTimePropertyName = "PunchTime"; private TimeSpan _punchTime = TimeSpan.Zero; public TimeSpan PunchTime { get { return _punchTime; } set { Set(PunchTimePropertyName, ref _punchTime, value); } } } ``` ViewModel : ``` public const string AttendanceCollectionPropertyName = "AttendanceCollection"; private ObservableCollection<AttendanceModel> _attendanceCollection = null; public ObservableCollection<AttendanceModel> AttendanceCollection { get { if (_attendanceCollection == null) { _attendanceCollection = new ObservableCollection<AttendanceModel>(); //_attendanceCollection.CollectionChanged+=_attendanceCollection_CollectionChanged; } return _attendanceCollection; } set { Set(AttendanceCollectionPropertyName, ref _attendanceCollection, value); } } ``` View : Issues I'm facing : 1) When a user ADD or DELETE a particular record from Inner DataGrid, I need to get notification in the View Model. I know it's possible by registering a collection changed event for an ObservableCollection. But how it's possible for an inner `ObservableCollection` ? 2) I need to get notifications in the viewmodel for any change in CheckIn or Checkout field in the Inner DataGrid, so that I can recalucate fields like TotalInTime, TotalOutTime etc. How can I do this ? I'm currently stuck with this scenario. Please suggest your valuable points.