ItemsSourceChanged Event using Attached Dependency Properties

If you stumble across this blog, you might have been searching for the non-existant ItemsSourceChanged event on a ListBox or ListView in Wpf.

Yeah… it isn’t there and it sucks.

But, there is a workable that I wouldn’t define as a hack. More of a Wpf extension. 😀

I am a huge fan of Attached Dependency Properties. They are a perfect tool to extend the functionality of closed controls. Attached Dependency Properties also circumvent the pain of created your own custom control.

I am going to use an attached dependency property to mimic an ItemsSource Changed event.

The full code :

   1: /// <summary>
   2: /// ItemsSourceChanged Behavior uses an Attached Dependency Property
   3: /// to add and raise a rotued event whenever an ItemsControl's ItemsSource property
   4: /// changes. Also looks for INotifyCollectionChanged on the ItemsSource and raises the
   5: /// event on every collection changed event
   6: /// </summary>
   7: public static  class ItemsSourceChangedBehavior
   8: {
   9:     #region ItemsSourceChanged Property
  10:  
  11:     /// <summary>
  12:     /// ItemsSourceChanged Attached Dependency Property with Callback method
  13:     /// </summary>
  14:     public static readonly DependencyProperty ItemsSourceChangedProperty =
  15:                                               DependencyProperty.RegisterAttached("ItemsSourceChanged",
  16:                                               typeof(bool), typeof(ItemsSourceChangedBehavior),
  17:                                               new FrameworkPropertyMetadata(false, OnItemsSourceChanged));
  18:  
  19:     /// <summary>
  20:     /// Static Get method allowing easy Xaml usage and to simplify the
  21:     /// GetValue process
  22:     /// </summary>
  23:     /// <param name="obj">The dependency obj.</param>
  24:     /// <returns>True or False</returns>
  25:     public static bool GetItemsSourceChanged(DependencyObject obj)
  26:     {
  27:         return (bool)obj.GetValue(ItemsSourceChangedProperty);
  28:     }
  29:  
  30:     /// <summary>
  31:     /// Static Set method allowing easy Xaml usage and to simplify the
  32:     /// Setvalue process
  33:     /// </summary>
  34:     /// <param name="obj">The obj.</param>
  35:     /// <param name="value">if set to <c>true</c> [value].</param>
  36:     public static void SetItemsSourceChanged(DependencyObject obj, bool value)
  37:     {
  38:         obj.SetValue(ItemsSourceChangedProperty, value);
  39:     }
  40:  
  41:     /// <summary>
  42:     /// Dependency Property Changed Call Back method. This will be called anytime
  43:     /// the ItemsSourceChangedProperty value changes on a Dependency Object
  44:     /// </summary>
  45:     /// <param name="obj">The obj.</param>
  46:     /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
  47:     private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  48:     {
  49:         ItemsControl itemsControl = obj as ItemsControl;
  50:  
  51:         if (itemsControl == null)
  52:             return;
  53:  
  54:         bool oldValue = (bool)e.OldValue;
  55:         bool newValue = (bool)e.NewValue;
  56:  
  57:         if (!oldValue && newValue) // If changed from false to true
  58:         {           
  59:             // Create a binding to the ItemsSourceProperty on the ItemsControl
  60:             Binding b = new Binding
  61:                             {
  62:                                 Source = itemsControl,
  63:                                 Path = new PropertyPath(ItemsControl.ItemsSourceProperty)
  64:                             };
  65:  
  66:             // Since the ItemsSourceListenerProperty is now bound to the
  67:             // ItemsSourceProperty on the ItemsControl, whenever the 
  68:             // ItemsSourceProperty changes the ItemsSourceListenerProperty
  69:             // callback method will execute
  70:             itemsControl.SetBinding(ItemsSourceListenerProperty, b);
  71:         }
  72:         else if (oldValue && !newValue) // If changed from true to false
  73:         {
  74:             // Clear Binding on the ItemsSourceListenerProperty
  75:             BindingOperations.ClearBinding(itemsControl, ItemsSourceListenerProperty);
  76:         }
  77:     }
  78:  
  79:     #endregion
  80:  
  81:     #region Items Source Listener Property
  82:  
  83:     /// <summary>
  84:     /// The ItemsSourceListener Attached Dependency Property is a private property
  85:     /// the ItemsSourceChangedBehavior will use silently to bind to the ItemsControl
  86:     /// ItemsSourceProperty.
  87:     /// Once bound, the callback method will execute anytime the ItemsSource property changes
  88:     /// </summary>
  89:     private static readonly DependencyProperty ItemsSourceListenerProperty =
  90:         DependencyProperty.RegisterAttached("ItemsSourceListener",
  91:                                             typeof(object), typeof(ItemsSourceChangedBehavior),
  92:                                             new FrameworkPropertyMetadata(null, OnItemsSourceListenerChanged));
  93:  
  94:  
  95:     /// <summary>
  96:     /// Dependency Property Changed Call Back method. This will be called anytime
  97:     /// the ItemsSourceListenerProperty value changes on a Dependency Object
  98:     /// </summary>
  99:     /// <param name="obj">The obj.</param>
 100:     /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
 101:     private static void OnItemsSourceListenerChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
 102:     {
 103:         ItemsControl itemsControl = obj as ItemsControl;
 104:         
 105:         if (itemsControl == null)
 106:             return;
 107:  
 108:         INotifyCollectionChanged collection = e.NewValue as INotifyCollectionChanged;
 109:  
 110:         if (collection != null)
 111:         {
 112:             collection.CollectionChanged += delegate
 113:                                                 {
 114:                                                     itemsControl.RaiseEvent(new RoutedEventArgs(ItemsSourceChangedEvent));
 115:                                                 };
 116:  
 117:         }
 118:  
 119:         if (GetItemsSourceChanged(itemsControl))
 120:             itemsControl.RaiseEvent(new RoutedEventArgs(ItemsSourceChangedEvent));
 121:     }
 122:  
 123:     #endregion
 124:  
 125:     #region Items Source Changed Event
 126:  
 127:     /// <summary>
 128:     /// Routed Event to raise whenever the ItemsSource changes on an ItemsControl
 129:     /// </summary>
 130:     public static readonly RoutedEvent ItemsSourceChangedEvent =
 131:         EventManager.RegisterRoutedEvent("ItemsSourceChanged", 
 132:                                          RoutingStrategy.Bubble, 
 133:                                          typeof(RoutedEventHandler),
 134:                                          typeof(ItemsControl));
 135:  
 136:     #endregion
 137: }

 

By setting the ItemsSourceChanged property on any ItemsControl to true, the ItemsSourceListener property will be bound to the ItemsSource property. The ItemsSourceListener callback will be executed anytime the ItemsSource changes and therefore can raise the ItemsSourceChanged routed event.

Also if the ItemsSource implements INotifyCollectionChanged, I added a delegate so that on the CollectionChanged event, the ItemsSourceChanged event will also be raised.

Feel free to use the code and adapt it to your needs.

Peace out!

JB

Advertisements