Results 1 to 5 of 5

Thread: Binding to a read-only property that returns a NEW collection of objects

  1. #1
    PowerPoster
    Join Date
    Apr 07
    Location
    The Netherlands
    Posts
    5,070

    Binding to a read-only property that returns a NEW collection of objects

    Hi,

    I am creating a control that resembles the 'month view calendar' in Outlook. It has one 'block' for each day and those blocks can contain appointments. The 'blocks' are called DayControls, and the appointments are instances of an Appointment class, whose important properties are the Subject and StartTime (DateTime) of the appointment.

    The Calendar control hosts a collection of DayControls via the DayControls property:
    csharp Code:
    1. private readonly ObservableCollection<DayControl> _InternalDayControls;
    2.         private readonly DayControlCollection _DayControls;
    3.         public DayControlCollection DayControls { get { return _DayControls; } }
    The DayControlCollection inherits ReadOnlyObservableCollection<DayControl> , such that DayControls cannot be added/edited from the outside of the calendar. When I add/remove DayControls I do that from the _InternalDayControls variable, which the _DayControls read-only collection wraps.

    Furthermore, the Calendar hosts a property Appointments, with a similar system;
    csharp Code:
    1. private readonly ObservableCollection<Appointment> _InternalAppointments;
    2.         private readonly AppointmentCollection _Appointments;
    3.         public AppointmentCollection Appointments { get { return _Appointments; } }
    Appointments can only belong to one single Day (not true in Outlook, but that is the case in my application) which is determined by their StartTime property.

    To add/remove appointments, I created a public AddAppointment and RemoveAppointment method. The Appointments property is a ReadOnlyObservableCollection again, because I don't want users adding new appointments to the Appointments collection directly. The reason will become clear in a moment.
    csharp Code:
    1. public void AddAppointment(Appointment appointment)
    2.         {
    3.             if (appointment == null) return;
    4.             _InternalAppointments.Add(appointment);
    5.             this.OnPropertyChanged(() => this.Appointments);
    6.         }

    The DayControls show the Appointments specific to that day in a ListBox. Since I want to data-bind that ListBox to a collection of Appointments, I needed an Appointments property in each DayControl as well. I made that into a read-only property that returns a new ObservableCollection<Appointment>, to which I add all the appointments that belong to this specific day:
    csharp Code:
    1. public ObservableCollection<Appointment> Appointments
    2.         {
    3.             get
    4.             {
    5.                 var appointments = new ObservableCollection<Appointment>();
    6.                 foreach (var appointment in this.Calendar.Appointments.FromDate(this.Date.Date))
    7.                 {
    8.                     appointments.Add(appointment);
    9.                 }
    10.                 return appointments;
    11.             }
    12.         }
    As you can see, the DayControl has a reference to its parent Calendar, from which I request all Appointments that have their StartTime set to the date of this current DayControl.


    This seems to work fine, except for one crucial issue: the ListBox does not know when to update its data!

    Back in the Calendar control I had a method AddAppointment, where I add an Appointment to the Calendar._InternalAppointments method (which adds it to the read-only Appointments property). But the DayControl.Appointments property (to which the ListBox is bound) is not updated, it will only return the new data when its getter is called... In other words; the DayControl.Appointments property does not notify the ListBox of changes (and it can't, because it is created 'on-the-fly' when required).

    For this reason, I can add a new appointment, but the DayControl in question does not display it because its ListBox does not know that the Appointments property has changed.


    One 'dirty' workaround I've come up with is just to raise the PropertyChanged event (with property "Appointments"), on the DayControl in question. So the AddAppointment method now looks like this:
    csharp Code:
    1. public void AddAppointment(Appointment appointment)
    2.         {
    3.             if (appointment == null) return;
    4.             _InternalAppointments.Add(appointment);
    5.             this.OnPropertyChanged(() => this.Appointments);
    6.  
    7.             // Also notify the DayControl to update its data
    8.             var day = this.DayControls.FromAppointment(appointment);
    9.             if (day != null) day.OnPropertyChanged(() => day.Appointments);
    10.         }
    This works, but it seems like a dirty workaround... The OnPropertyChanged method shouldn't be called from the outside (I had to make it public just to try this).

    Am I right, is this a 'hack', or is this the way it should be done? Perhaps instead of making the OnPropertyChanged public I should make a public method specifically to call the OnPropertyChanged:
    csharp Code:
    1. public void UpdateAppointments()
    2. {
    3.     this.OnPropertyChanged(() => this.Appointments);
    4. }
    That's better, but still doesn't seem 'correct'.


    What should I be doing instead?

  2. #2
    Frenzied Member Evil_Giraffe's Avatar
    Join Date
    Aug 02
    Location
    Suffolk, UK
    Posts
    1,879

    Re: Binding to a read-only property that returns a NEW collection of objects

    When you add an appointment to the DayControlCollection, I would also add it to the relevant DayControl as well, exposing them as a ReadOnlyObservableCollection to the UI just as you do at the DayControlsCollection level.

    I would also suggest that having a collection of controls is working somewhat counter to the spirit of WPF. You should be exposing collections of domain classes - so instead of a DayControlCollection containing DayControls, I would expect a DayCollection containing Day instances. Your XAML can then define a DataTemplate that says for a Day instance, render it with a DayControl. The DayCollection would be bound as the ItemsSource of a CollectionControl which has its ItemsPanel template set to lay it out in the calendar style, and so on.

  3. #3
    Frenzied Member Evil_Giraffe's Avatar
    Join Date
    Aug 02
    Location
    Suffolk, UK
    Posts
    1,879

    Re: Binding to a read-only property that returns a NEW collection of objects

    Quote Originally Posted by NickThissen View Post
    csharp Code:
    1. public void AddAppointment(Appointment appointment)
    2.         {
    3.             if (appointment == null) return;
    4.             _InternalAppointments.Add(appointment);
    5.             this.OnPropertyChanged(() => this.Appointments);
    6.         }
    You do not need to raise the PropertyChanged event for Appointments property here. The value of the property has not changed (it is still the same collection returned). What has changed is the contents of the collection, but WPF is notified about that via the INotifyCollectionChanged interface of the observable nature of the collection.


    And my previous post was referring to this method here, suggesting something like the following (using the post reply box as my IDE, so don't expect this to work straight off ):

    csharp Code:
    1. public void AddAppointment(Appointment appointment)
    2.         {
    3.             if (appointment == null) return;
    4.             _InternalAppointments.Add(appointment);
    5.             foreach (DayControl dayControl in
    6.                         _InternalDayControls
    7.                             .Where(dayCtl => dayCtl.StartDate == appointment.Date))
    8.                 dayControl.AddAppointment(appointment);
    9.         }

    (Yes, you should only have a single matching DayControl, but I prefer to just iterate over everything rather than messing about extracting the single value).

  4. #4
    Frenzied Member Evil_Giraffe's Avatar
    Join Date
    Aug 02
    Location
    Suffolk, UK
    Posts
    1,879

    Re: Binding to a read-only property that returns a NEW collection of objects

    Another option is to bind the ListBox to the complete collection of appointments exposed at the top level, and then grab the CollectionView from it and alter that depending on which Day is selected by setting the Filter on it. This will then only display the appointments from the collection that match the predicate supplied (appointment.Date == selectedDay.Date)

  5. #5
    PowerPoster
    Join Date
    Apr 07
    Location
    The Netherlands
    Posts
    5,070

    Re: Binding to a read-only property that returns a NEW collection of objects

    Quote Originally Posted by Evil_Giraffe View Post
    I would also suggest that having a collection of controls is working somewhat counter to the spirit of WPF. You should be exposing collections of domain classes - so instead of a DayControlCollection containing DayControls, I would expect a DayCollection containing Day instances. Your XAML can then define a DataTemplate that says for a Day instance, render it with a DayControl. The DayCollection would be bound as the ItemsSource of a CollectionControl which has its ItemsPanel template set to lay it out in the calendar style, and so on.
    Completely agree, but I did not get that to work. I needed this in a hurry so I didn't have time to figure out how to do it exactly. The problem is was the layout of the day controls; if the window is very wide of course the day controls should become wider as well (in my case it just placed more controls on one line), and also the day names above (Sunday, Saturday, ...) would need to stretch along with the day controls (they should of course stay centered above it).
    There certainly is a way better way to construct my control but I simply didn't see it, so I did it the easy way. I guess it is coming back to bite me

    Quote Originally Posted by Evil_Giraffe View Post
    You do not need to raise the PropertyChanged event for Appointments property here. The value of the property has not changed (it is still the same collection returned). What has changed is the contents of the collection, but WPF is notified about that via the INotifyCollectionChanged interface of the observable nature of the collection.
    True, I was just trying if that helped.

    Quote Originally Posted by Evil_Giraffe View Post
    Another option is to bind the ListBox to the complete collection of appointments exposed at the top level, and then grab the CollectionView from it and alter that depending on which Day is selected by setting the Filter on it. This will then only display the appointments from the collection that match the predicate supplied (appointment.Date == selectedDay.Date)
    I like that even better; I've never heard of 'grabbing a CollectionView'. I suppose you can easily filter a collection and display only the part that matches a predicate? The DayControl could then just display all appointments and just filter them on the date.

    Thanks, I will check this out.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •