2

Context

I'm working on a .net WPF Application to manage sequential and hierarchical activities featuring (top to bottom):

  • Main FirstLevel activity
  • FirstLevel activity is divided into SecondLevel activities with their own StartTime and Duration parameters
  • Each SecondLevel activity is further divided into ThirdLevel activities with their own StartTime and Duration parameters

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

Before After


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();
 }
 }
}
EldHasp
8,7302 gold badges11 silver badges34 bronze badges
asked Jun 1, 2025 at 4:21
4
  • Have a look at reactiveui.net/docs/handbook/collections.html Commented Jun 1, 2025 at 6:03
  • You have a mistake in class names. For "ThirdLevel Activity Class" in the code fragment you have a class named SecondLevelViewModel. I took the liberty of editing the text of your question to correct this inaccuracy. Commented Jun 1, 2025 at 7:25
  • Hi SirRufo, I've taken a quick glance at the provided link, it sure does offer a lot of nice features, but i think the answer below by @Lamp is simple enough and fits my current needs just fine, although your link may be of use for me in the future, so thank you once again! Commented Jun 1, 2025 at 7:26
  • @EldHasp oof, silly mistake by me, my bad and good catch, and thanks for taking your time to make the necessary corrections, much appreaciated! Commented Jun 1, 2025 at 7:31

2 Answers 2

3
// 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?

answered Jun 1, 2025 at 7:09
Sign up to request clarification or add additional context in comments.

2 Comments

Spot on! i was trying to do something similar by casting the type directly (i.e. ...(ThirdLevelViewModel)Sender..) to get the index of the sender within the Collection but it just wouldn't work (as it should, duh!), and I tried using the 'as' operator as well but didn't quite get the syntax right. Thanks a bunch and much appreciated!!
Instead of if (e.PropertyName == "Duration") better write if (e.PropertyName == nameof(ThirdLevelViewModel.Duration)). It is safer, because it would survive refactoring.
2

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();
 }
answered Jun 1, 2025 at 7:49

1 Comment

thanks for providing a clear and well explained alternative, as well as all the additional switch cases which definitely expands my vocabulary on how one may need / use such cases if and when the need arises. I'll go ahead and experiment with them later on, thank you once again!

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.