Search code examples
wpfobservablecollectioninotifypropertychangedlistviewitem

How to update multiple Observable Collections in a WPF application?


I have written a WPF application that lets a user add and view a list of project files that he needs to build in batch mode, and then view the run time progress of each file as it is being built one-by-one. For simplicity, I have kept two listviews such that one displays the list of projects that the user has added, and the second one displays the the build progress of a file as it is being built. Both the listviews have been implemented as ObservableCollections. The first listview works fine and I can see it getting updated as soon as I add a file. However, the second listview shows the build status of a file only after it is completed, so I have to wait until a project is built to see the project status, which is not the way I had implemented it. In the code, for the second listview, I set the status property for a file as "Running", and then call NotifyPropertyChanged("status") so as to update it on the listview (so that user can know that the file is being built), and thereafter run a command to build the file. After the build is completed, I change the status property for the file as "Completed", and then again call NotifyPropertyChanged("status"). However, as I said, I see the status (as "Completed") only after the file has been built. (The first call to NotifyPropertyChanged("status") does nothing.) I would like the listview to update soon after I run NotifyPropertyChanged("status") after changing the status as "Running". Any help would be greatly appreciated.

namespace Names { 
    public partial class MainWindow : Window 
    { 
    public ObservableCollection<projectRecord> records;
    public ObservableCollection<projectStatus> statusrecords;
    public MainWindow()
    {
        InitializeComponent();
        records = new ObservableCollection<projectRecord>();
        statusrecords = new ObservableCollection<projectStatus>();
        listName.ItemsSource = records;//first listview
        statusList.ItemsSource = statusrecords;//second listview
    }

    public class projectRecord : INotifyPropertyChanged
    {
        //some properties
        public string lblProjectName { get; set; }
        
        //some methods 
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }    

    public class projectStatus : INotifyPropertyChanged
    {
        //some properties
        public string status { get; set; }
        
        //some methods 
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    } 

    private void ButtonBuild_Click(object sender, RoutedEventArgs e) 
    {
       //fetch project records one by one
       for(i=0;i<records.Count;i++)
        {
          projectRecordName = records[i].lblProjectName; 

          statusrecords.Add(new projectStatus(projectRecordName));

          statusrecords[i].status = "Running";
          statusrecords[i].NotifyPropertyChanged("status");//this does not updates the list view
         
          BuildProjectFile(projectRecordName);

          statusrecords[i].status = "Completed";
          statusrecords[i].NotifyPropertyChanged("status");//this does updates the list view
        }
    }
}

}


Solution

  • Apart from the few strange things I'll mention later, I think the problem could be that the BuildProjectFile is synchronous and may be blocking the thread, preventing the status text from updating. Turning it into an async method might help.

    I've also done several tweaks.

    1. It looks like the ProjectRecord doesn't need to implement INotifyPropertyChanged as you're not using it, so I removed it.
    2. In the ProjectStatus class, I made the NotifyPropertyChanged method private and moved the invocation to the setter of Status. That's usually how its done, you don't want to allow external code to invoke (or forget invoking) NotifyPropertyChanged.

    Here's the code:

    namespace Names { 
        public partial class MainWindow : Window 
        { 
            private readonly ObservableCollection<ProjectRecord> records = new ObservableCollection<ProjectRecord>();
            private readonly ObservableCollection<ProjectStatus> statusrecords = new ObservableCollection<ProjectStatus>();
    
            public MainWindow()
            {
                InitializeComponent();
                
                listName.ItemsSource = records;
                statusList.ItemsSource = statusrecords;
            }
    
            public class ProjectRecord
            {
                public string ProjectName { get; set; }
            }    
    
            public class ProjectStatus : INotifyPropertyChanged
            {
                private string _status;
                public string Status 
                {
                    get => _status;
                    set {
                        _status = value;
                        NotifyPropertyChanged(nameof(Status));
                    }    
                }
                
                public event PropertyChangedEventHandler PropertyChanged;
    
                private void NotifyPropertyChanged(String propertyName)
                {
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                }
            } 
    
            private async void ButtonBuild_Click(object sender, RoutedEventArgs e) 
            {
                for(i = 0; i < records.Count; i++)
                {
                    projectRecordName = records[i].ProjectName; 
    
                    statusrecords.Add(new projectStatus(projectRecordName));
    
                    statusrecords[i].Status = "Running";
                    
                    await BuildProjectFileAsync(projectRecordName);
    
                    statusrecords[i].Status = "Completed";
                }
            }
        }
    }
    

    Does this help?