Context
I'm working on a .net WPF Application to manage sequential and hierarchical activities featuring (top to bottom):
- Main
FirstLevelactivity FirstLevelactivity is divided intoSecondLevelactivities with their ownStartTimeandDurationparameters- Each
SecondLevelactivity is further divided intoThirdLevelactivities with their ownStartTimeandDurationparameters
I've managed to handle the necessary updates based on PropertyChanged events or CollectionChanged events without too much trouble, but I'm having a particular issue as enclosed in (***) in the Code Snippet and as outlined in the Issue section below
Issue
I can't figure out a way of handling the update of StartTime property of only the subsequent ThirdLevelViewModel objects in the ObservableCollection when one of their Duration is changed, e.g.:
C# Code Snippets (Simplified)
SecondLevel Activity Class
// Derived from BindableBase Class which implements InotifyPropertyChanged
public class SecondLevelViewModel : BindableBase
{
// constructor
public SecondLevelViewModel()
{
// relevant instantiations
ThirdLevels.CollectionChanged += ThirdLevels_CollectionChanged;
foreach (ThirdLevelViewModel thirdLevel in ThirdLevels)
{
thirdLevel.PropertyChanged += thirdLevel_PropertyChanged;
}
}
// properties
public ObservableCollection<ThirdLevelViewModel> ThirdLevels
{
// relevant Get and Set accessors
...
}
// CollectionChanged Event Handler
private void ThirdLevels_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// switch implementation
...
}
// PropertyChanged Event Handler
private void thirdLevel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// other relevant updates on thirdLevel object's PropertyChanged Event
...
// *** Update StartTime value of subsequent Items in the Collection ***
}
}
ThirdLevel Activity Class
// Derived from BindableBase Class which implements InotifyPropertyChanged
public class ThirdLevelViewModel : BindableBase
{
// constructor
public ThirdLevelViewModel()
{
// relevant instantiations
...
// Updates value of Duration property
// based on Timer property which is a DispatcherTimer object
// called by bound button on UI
...
}
public TimeOnly StartTime
{
get { ... }
set
{
...
OnPropertyChanged();
}
}
public TimeSpan Duration
{
get { ... }
set
{
...
OnPropertyChanged();
}
}
}
2 Answers 2
// PropertyChanged Event Handler
private void thirdLevel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// other relevant updates on thirdLevel object's PropertyChanged Event
...
// *** Update StartTime value of subsequent Items in the Collection ***
if (e.PropertyName == "Duration")
{
var index = ThirdLevels.IndexOf(sender as ThirdLevelViewModel);
for (int i = index + 1; i < ThirdLevels.Count...
}
}
It's super easy, any problem?
2 Comments
if (e.PropertyName == "Duration") better write if (e.PropertyName == nameof(ThirdLevelViewModel.Duration)). It is safer, because it would survive refactoring.For WPF I recommend using BindingList instead of ObservableCollection in such cases. In the IBindingList.ListChanged event you get the index of the changed element, the PropertyDescriptor of the changed property and other arguments needed to identify the changes.
Here is an example of using such a collection:
// Derived from BindableBase Class which implements InotifyPropertyChanged
public class SecondLevelViewModel : BindableBase
{
// constructor
public SecondLevelViewModel()
{
// relevant instantiations
ThirdLevels.ListChanged += ThirdLevels_ListChanged;
//foreach (ThirdLevelViewModel thirdLevel in ThirdLevels)
//{
// thirdLevel.PropertyChanged += thirdLevel_PropertyChanged;
//}
}
// ListChanged Event Handler
private static void ThirdLevels_ListChanged(object? sender, ListChangedEventArgs e)
{
ArgumentNullException.ThrowIfNull(sender);
BindingList<ThirdLevelViewModel> thirds = (BindingList<ThirdLevelViewModel>)sender!;
// The example shows only the processing of the "ItemChanged" change type.
// The rest of the values are given only to show what types of changes there are.
// If they do not need to be processed, they can be deleted.
switch (e.ListChangedType)
{
case ListChangedType.ItemChanged:
// Handling change of the ThirdLevelViewModel property
{
// We process only the next element.
// Its change will cause ListChanged for it and
// the next item will be processed in it.
// And so on down the chain to the end of the collection.
if (e.NewIndex < thirds.Count - 1)
{
ThirdLevelViewModel current = thirds[e.NewIndex];
ThirdLevelViewModel next = thirds[e.NewIndex + 1];
switch (e.PropertyDescriptor?.Name)
{
case nameof(ThirdLevelViewModel.Duration):
next.Duration = current.Duration + TimeSpan.FromSeconds(1);
break;
case nameof(ThirdLevelViewModel.StartTime):
next.StartTime = current.StartTime.AddMinutes(1);
break;
default:
break;
}
}
}
break;
case ListChangedType.Reset:
break;
case ListChangedType.ItemAdded:
break;
case ListChangedType.ItemDeleted:
break;
case ListChangedType.ItemMoved:
break;
case ListChangedType.PropertyDescriptorAdded:
break;
case ListChangedType.PropertyDescriptorDeleted:
break;
case ListChangedType.PropertyDescriptorChanged:
break;
default:
break;
}
}
// properties
public BindingList<ThirdLevelViewModel> ThirdLevels { get; } = new();
}
1 Comment
Explore related questions
See similar questions with these tags.
SecondLevelViewModel. I took the liberty of editing the text of your question to correct this inaccuracy.