Search code examples
c#wpfdata-binding

How to binding Time-Dependent-Property of all the items in ObservableCollection<Class-with-a-Time-Dependent-Property> to a TextBox?


I have lots kind of tasks (instances of TaskClass) stored in a ObservableCollection named TaskCollection. In class TaskClass, there was a TimeLeft property which changed dynamically when the task is running.

class TaskClass:INotifyPropertyChanged
{
    TimeSpan TimeLeft{get;set;}
}

When one or more tasks were running, user could add some new tasks to TaskCollection.When a task is running, the task's TimeLeft property changed (increase or decrease).

A TextBox was used in the UI to display the total time needed for all tasks. I've tried to use a multi-bind to both TaskCollection and TaskCollection.Count. The total time will display in the TextBox just after user add/remove tasks. But when the existed task running (the TimeLeft property changes), the TextBox cannot notify.

How to binding TimeLeft prop of all the tasks stored in ObservableCollection to a TextBox, so that the total time left could display in time?


Solution

  • But when the existed task running (the TimeLeft property changes), the TextBox cannot notify.

    This is because there is no notification for any of the data-bound properties when an individual TimeLeft property is set.

    What you should do is to add a property to the view model that returns the sum of all TimeSpans. You would then implement the logic to set this property in the view model where it belongs. Something like this:

    public class ViewModel : INotifyPropertyChanged
    {
        public ViewModel()
        {
            Tasks.CollectionChanged += OnTasksCollectionChanged;
        }
    
        public ObservableCollection<TaskClass> Tasks { get; } = 
            new ObservableCollection<TaskClass>();
    
        public TimeSpan TotalTimeLeft => new TimeSpan(Tasks.Sum(x => x.TimeLeft.Ticks));
    
        private void OnTasksCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
                foreach (object task in e.NewItems)
                    (task as INotifyPropertyChanged).PropertyChanged
                        += new PropertyChangedEventHandler(OnTaskPropertyChanged);
    
            if (e.OldItems != null)
                foreach (object task in e.OldItems)
                    (task as INotifyPropertyChanged).PropertyChanged
                        -= new PropertyChangedEventHandler(OnTaskPropertyChanged);
    
            NotifyPropertyChanged(nameof(TotalTimeLeft));
        }
    
        private void OnTaskPropertyChanged(object sender, PropertyChangedEventArgs e) =>
            NotifyPropertyChanged(nameof(TotalTimeLeft));
    
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }