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.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With