locked
Data binding not updated for CollectionViewSource which source has been grouped or sorted

    Question

  • I have a Windows Store app (or Metro style / WinRT / Windows Runtime) that has a CollectionViewSource that is bound to a ListView in the codebehind file. Without sorting and/or grouping all works fine and the UI gets updated. But as soon as I sort or group the CollectionViewSource's Source, the UI stops updating. Set the binding in MainPage:

    public MainPage()
    {
        InitializeComponent();
    
        ViewModel = new MainPageVm();
        DataContext = ViewModel;
    
        Binding myBinding = new Binding();
        myBinding.Mode = BindingMode.TwoWay;
        myBinding.Source = ViewModel.UpcomingAppointments;
        UpcomingListView.SetBinding(ListView.ItemsSourceProperty, myBinding);
    
        //Timer to update the UI periodically
        var Timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(60)};
        Timer.Tick += (o, e) => ViewModel.LoadData();
        Timer.Start();
    }

    The relevant part of the viewmodel:

    public class MainPageVm : INotifyPropertyChanged
    {
        public MainPageVm()
        {
            Appointments = new ObservableCollection<Appointment>();
            Appointments.CollectionChanged += delegate { NotifyPropertyChanged("UpcomingAppointments"); };
        }
    
    
        public ObservableCollection<Appointment> Appointments
        {
            get { return appointments; }
            set
            {
                if (appointments != value)
                {
                    appointments = value;
                    NotifyPropertyChanged();
                    NotifyPropertyChanged("UpcomingAppointments");
                }
            }
        }
    
    
        public CollectionViewSource UpcomingAppointments
        {
            get
            {
                return new CollectionViewSource
                {
                   //removing Where an Groupby and set IsSourceGrouped = false get the UI updated again
                    Source = Appointments
                        .Where(a => a.Start > DateTime.UtcNow) 
                        .GroupBy(x => x.Day),
                    IsSourceGrouped = true
                }; 
            }
        }
    
    
        public async Task LoadData()
        {
            var Repo = new WebserviceRepository();
            await Repo.GetAppointments();
    
            Appointments.Clear;
            foreach (Appointment Appointment in Repo.Appointments)
            {
                Appointments.Add(Appointment);
            }
            NotifyPropertyChanged("UpcomingAppointments");
            NotifyPropertyChanged("Appointments");
        }
    
    
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }    

    And the relevant XAML part:

    <ListView Name="UpcomingListView" ItemTemplate="{StaticResource AppointmentListTemplate}">
    <ListView.GroupStyle>
        <GroupStyle HidesIfEmpty="False">
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <Grid>
                        <TextBlock Text="{Binding Key}" />
                    </Grid>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </ListView.GroupStyle>

    And finally the Appointment class:

    public class Appointment
    {
        public Guid AppointmentId { get; set; }
        public string Description { get; set; }
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        public string Day
        {
            get
            { 
               if (this.Start.Date == DateTime.Now.Date){return "Today";}
               if (this.Start.Date == (DateTime.Now.AddDays(1).Date)) { return "Tomorrow";}
               If (this.Start.Date == (DateTime.Now.AddDays(2).Date)){ return "Day After Tomorrow";}
               return string.Empty;         
            }
        }
    }

    So deleting the .Where and .Sort and set IsSourceGrouped to false makes the UI update again, but of course unsorted and not the desired grouping.

    When I set data in the constructor of the ViewModel, that data is displayed (grouped or not) but not updated

    As you can see I call NotifyPropertyChanged whenever LoadData is run, just to be sure that it is called. I also run NotifyPropertyChanged after the Appointments OberservableCollection-s Collection has changed.

    Apparently the Where filtering and the GroupBy grouping needs some other tweaks that I am not aware of. I see a lot of question on forums about grouping, but it seems that they are about WPF (and that is different) or set all the bindings and static resources in XAML (and I don't want to do that).

    So my basic question is: How can I group and sort the UpcomingAppointments in a ListView using the C# codebehind file?


    • Edited by Richard D Tuesday, December 30, 2014 12:22 PM
    Tuesday, December 30, 2014 12:04 PM

Answers

  • For the getter of your UpcomingAppointments property to even get called again when you call NotifyPropertyChanged("UpcomingAppointments") from the LoadData method, you should specify the Path of the Binding in the constructor of the Page:

          Binding myBinding = new Binding();
          myBinding.Path = new PropertyPath("UpcomingAppointments");
          UpcomingListView.SetBinding(ListView.ItemsSourceProperty, myBinding);


    But you main issue is that the Where and GroupBy LINQ methods will return a brand new IEnumerable which means that any item you add to the Appointments collection won't automatically be displayed in the ListView since this one is bound to the CollectionViewSource. And why are you creating a new CollectionViewSource in the getter of the UpcomingAppointments? Create one CollectionViewSource and set its Source in the LoadData() method:

    View Model:

    public MainPageVm()
        {
            Appointments = new ObservableCollection<Appointment>();
            Appointments.CollectionChanged += delegate { NotifyPropertyChanged("UpcomingAppointments"); };
    
          UpcomingAppointments = new CollectionViewSource()
          {
            Source = Appointments.Where(a => a.Start > DateTime.UtcNow).GroupBy(x => x.Day),
     IsSourceGrouped = true
    
          };
        }
    
     private CollectionViewSource _upcomingAppointments;
        public CollectionViewSource UpcomingAppointments {
          get {
            return _upcomingAppointments;
          }
          set {
            _upcomingAppointments = value;
            NotifyPropertyChanged();
          }
        }
    
     
    
        public async Task LoadData() {
          var Repo = new WebserviceRepository();
          await Repo.GetAppointments();
    
          //Appointments.Clear();
          foreach (Appointment Appointment in Repo.Appointments) {
            Appointments.Add(Appointment);
          }
    
    
            this.UpcomingAppointments.Source = Appointments
              .Where(a => a.Start > DateTime.UtcNow)
                  .GroupBy(x => x.Day);
    
    
            this.UpcomingAppointments.View.Refresh();
    
    
          NotifyPropertyChanged("UpcomingAppointments");
          NotifyPropertyChanged("Appointments");
        }
    


    Page:

    public MainPage() {
          InitializeComponent();
    
          ViewModel = new MainPageVm();
          DataContext = ViewModel;
    
          Binding myBinding = new Binding();
          myBinding.Source = ViewModel.UpcomingAppointments;
          UpcomingListView.SetBinding(ListView.ItemsSourceProperty, myBinding);
    
          //Timer to update the UI periodically
          var Timer = new DispatcherTimer
          {
            Interval = TimeSpan.FromSeconds(60)
          };
          Timer.Tick += (o, e) => ViewModel.LoadData();
          Timer.Start();
    
        }
    

    Hope that helps.

    If you need any further help on this I suggest you upload a reproducable sample of your issue to OneDrive and post the link to it here.

    Please remember to mark helpful posts as answer and please remember that a new question deserves a new thread.

     

     

     

    Tuesday, December 30, 2014 3:57 PM