Nested ObservableCollection - profile notification from child to parent

I am developing a WPF application with the MVVM Light Toolkit . I just want to display a nested Observablecollection that contains data about employee visitors in the DataGrid and performs some CRUD functions in the internal grid, and based on these changes, I have to automatically recalculate the external collection record. An internal collection ( PunchDetailModels ) is displayed in the RowDetailsTemplate DataGrid .

Here are 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: enter image description here

Problems I am facing:

1) When a user ADD or DELETE a specific record from Inner DataGrid, I need to get a notification in the View Model. I know that this is possible by registering a collection change event for an ObservableCollection. But how is this possible for an internal Observablecollection ?

2) I need to get notifications in the viewmodel for any changes to the CheckIn or Checkout field in the internal DataGrid so that I can override fields like TotalInTime, TotalOutTime, etc.

How can i do this? I am currently sticking to this scenario. Please offer your valuable points.

+6
source share
1 answer

I assume that the ObservableObject class is your own implementation of the INotifyPropertyChanged interface. Now, to solve your problems:

  • You must register in the CollectionChanged event in _punchDetailModels and raise the PropertyChanged event for this variable in the handler, for example:

      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. } 

Therefore, the view should reload automatically when adding or removing items from the internal collection.

  • There is no other way to subscribe to listen to PropertyChanged in these fields. What View does and what ViewModel should do. For instance:

      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 the PropertyChanged with the string argument to the CheckIn value in the AttendanceModel class if you consider it necessary (for example, in the handler method)

EDIT:

To answer your question:

"Come to the second - I need to recalculate the attendance model properties, such as InOutCount, TotalInTime, TotalOutTime in updating the PunchTime field."

Answer: you do not need to do anything in the ViewModel to "recount". The user interface subscribes to PropertyChange for InOutCount , FirstCheckIn ... and so on. This is due to Binding (it does this automatically).

So, all you have to do to tell the user interface that this model should be recounted is to call RaisePropertyChanged("InOutCount") , RaisePropertyChanged("FirstCheckIn") . The user interface will understand that it needs to GET these properties, and since you have these calculations in the getters properties, it will be recalculated.

So, I see that the UI needs to be recounted every time the INNER list is changed, so all you have to do is change the handler code to CollectionChanged for PunchDetailModels as follows:

 // 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 } 
+5
source

All Articles