ObservableCollection dependency property does not update when item in collection is deleted

17,542

Solution 1

A change within the collection won't trigger the OnSpecialDaysChanged callback, because the value of the dependency property hasn't changed. If you need to react to detect changes with the collection, you need to handle the event CollectionChanged event manually:

private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var calendar = d as RadCalendar;

  if (e.OldValue != null)
  {
    var coll = (INotifyCollectionChanged)e.OldValue;
    // Unsubscribe from CollectionChanged on the old collection
    coll.CollectionChanged -= SpecialDays_CollectionChanged;
  }

  if (e.NewValue != null)
  {
    var coll = (ObservableCollection<DateTime>)e.NewValue;
    calendar.DayTemplateSelector = new SpecialDaySelector(coll, GetSpecialDayTemplate(d));
    // Subscribe to CollectionChanged on the new collection
    coll.CollectionChanged += SpecialDays_CollectionChanged;
  }
}

private static void SpecialDays_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // handle CollectionChanged
}

Solution 2

If you have a collection-type dependency property keep the following in mind:

If your property is a reference type, the default value specified in dependency property metadata is not a default value per instance; instead it is a default value that applies to all instances of the type. [...]
To correct this problem, you must reset the collection dependency property value to a unique instance, as part of the class constructor call.

(see MSDN Collection-Type Dependency Properties)

To answer Sam's question (I just ran into the same problem):

Make your CollectionChanged-handler non-static and unsubscribe/re-subscribe on instance-level.

private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var calendar = d as RadCalendar;

  if (e.OldValue != null)
  {
    var coll = (INotifyCollectionChanged)e.OldValue;
    // Unsubscribe from CollectionChanged on the old collection of the DP-instance (!)
    coll.CollectionChanged -= calendar.SpecialDays_CollectionChanged;
  }

  if (e.NewValue != null)
  {
    var coll = (ObservableCollection<DateTime>)e.NewValue;
    calendar.DayTemplateSelector = new SpecialDaySelector(coll, GetSpecialDayTemplate(d));
    // Subscribe to CollectionChanged on the new collection of the DP-instance (!)
    coll.CollectionChanged += calendar.SpecialDays_CollectionChanged;
  }
}

private void SpecialDays_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // handle CollectionChanged on instance-level
}

Solution 3

This is just to add to the answer by Thomas. In my code I do interact with the DependencyObject's properties by creating a handler object localy like below:

private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var action = new NotifyCollectionChangedEventHandler(
            (o, args) =>
                {
                    var calendar = d as RadCalendar;

                    if (calendar!= null)
                    {
                        // play with calendar's properties/methods
                    }
                });

    if (e.OldValue != null)
    {
       var coll = (INotifyCollectionChanged)e.OldValue;
       // Unsubscribe from CollectionChanged on the old collection
       coll.CollectionChanged -= action;
    }

    if (e.NewValue != null)
    {
       var coll = (ObservableCollection<DateTime>)e.NewValue;
       // Subscribe to CollectionChanged on the new collection
       coll.CollectionChanged += action;
    }
}

Hope this is helpful to someone.

Share:
17,542

Related videos on Youtube

GoalMaker
Author by

GoalMaker

Updated on February 27, 2020

Comments

  • GoalMaker
    GoalMaker over 4 years

    I have a attached property of type ObservableCollection on a control. If I add or remove items from the collection, the ui does not update. However if I replace the collection within with a new one the ViewModel the ui does update.

    Can someone give me an example of what I need to do within the Dependency object so that it can handle changes within the collection?

    Part of the dependency object is listed below:

    public class RadCalendarBehavior : DependencyObject
    {
    private static void OnSpecialDaysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var calendar = d as RadCalendar;
      if (e.NewValue != null)
      {
        calendar.DayTemplateSelector = new SpecialDaySelector((ObservableCollection<DateTime>)e.NewValue, GetSpecialDayTemplate(d));
      }
    }
    
    public static ObservableCollection<DateTime> GetSpecialDays(DependencyObject obj)
    {
      return (ObservableCollection<DateTime>)obj.GetValue(SpecialDaysProperty);
    }
    
    public static void SetSpecialDays(DependencyObject obj, ObservableCollection<DateTime> value)
    {
      obj.SetValue(SpecialDaysProperty, value);
    }
    
    public static readonly DependencyProperty SpecialDaysProperty =
        DependencyProperty.RegisterAttached("SpecialDays", typeof(ObservableCollection<DateTime>), typeof(RadCalendarBehavior), new UIPropertyMetadata(null, OnSpecialDaysChanged));
    }
    }
    

    I understand that I need to register that the collection has changed, but I am unsure how to do this within the dependency property

  • GoalMaker
    GoalMaker over 13 years
    Thank you. Is it possible to get a reference of the dependency object within SpecialDays_CollectionChanged?
  • Thomas Levesque
    Thomas Levesque over 13 years
    Which dependency object ? You mean the RadCalendar ? Not easily... anyway, I'm not sure it would make sense: what if several instances of RadCalendar are bound to the same collection ?
  • GoalMaker
    GoalMaker over 13 years
    I see what you mean. I was trying to get the RadCalendar to no longer highlight a special day if it was removed from the collection, so I thought I might do this by creating a new SpecialDaySelector and setting the DayTemplateSelector of the Calendar control.
  • Sam
    Sam over 7 years
    This is helpful yes, I'm looking for a way to access the DependencyObject in the CollectionChangedEventHandler, and this seems to work. I find it hard to believe that this is the only way though... Are we doing something wrong ? I have an ObservableCollection DP, when items are added to it (collectionchanged event is fired...), I need to do stuff to the main control (like add sub-controls). And your solution is the only one I found to access the DependencyObj. Since this post have you found another way ?
  • Lukáš Koten
    Lukáš Koten over 5 years
    I would rather use IList<T> as a underlying type instead of ObservableCollection since you can always bind ItemsSource to collections different from ObservableCollection like array or List.
  • Lukáš Koten
    Lukáš Koten over 5 years
    Shows error "DependencyObject" does not contain a definition for "SpecialDays_CollectionChanged". You probably mean calendar.SpecialDays_CollectionChanged
  • Jürgen Böhm
    Jürgen Böhm about 3 years
    Your solution leads to problematic behaviour: Assume the ObservableCollection Dependency Property is bound first to an OC 'aoc' then to 'null' then again to 'aoc'. At the end 'aoc''s 'CollectionChanged' will be bound to two different values of 'var action' (= two different closures) as the first assigned closure is never removed.
  • Jürgen Böhm
    Jürgen Böhm about 3 years
    May I ask you to comment on the variation of your solution presented below by Matumba? I implemented it (suitably adapted) in my application and it seems to work. But maybe there is a pitfall too - I am not knowledgeable enough in C# and WPF to see fully through it. Previously I used nowaq's approach, but it led to faulty behaviour (see my comment below there).