I’m a big fan of attached properties as you can tell from my previous blogs Using a Generic Custom Attached Property Part 1 and Using a Generic Custom Attached Property Part 2. This blog could be part 3 in a way because it builds upon my work on generic attached properties. Attached properties can be used not only to store information but also to bring about custom behaviors to the objects they are attached to, which makes them like Blend behaviors but much easier to set in both XAML and code behind.
I found a design pattern I liked in a Telerik example for a custom filter row for a RadGrid. Basically when you attached a custom attached property to an object, it would create an instance of the custom attached property in the static value changed event handler and then perform the desired action upon the object.
Now that I had a good design pattern I found a way to build upon my previous generic attached property work and transform this design pattern into something that is strongly typed and generic. I extended the idea so that the instance of the class would also be persisted. To make it generic and strongly typed I was forced to use much of the same static class generic pattern I used in my generic attached property class with two generic types found in Using a Generic Custom Attached Property Part 2. The first generic type is the owner type, and the second type is the type of the attached property. However, in this new class I needed to add a third generic for the type of object that this attached property can be attached to. We also want it to be abstract because there will always be an implementation of it. So the declaration of this class looks like this.
/// <summary>
/// Use this class as an attached property that can handle value changed events and maintains the
/// associated object that is attached to.
/// </summary>
/// <typeparam name="O">The type of the owner class</typeparam>
/// <typeparam name="T">Type of the associated object that can be attached to</typeparam>
/// <typeparam name="A">Type of the attached property that is attached to the associated object</typeparam>
public abstract class AttachedPropertyAssociatedObject<O, T, A> : DependencyObject
where T : DependencyObject
where O : AttachedPropertyAssociatedObject<O, T, A>, new()
{ }
If you’re confused as to why we need the first O type generic type, then be sure to read Using a Generic Custom Attached Property Part 2. You can get around all of this with a bunch of copying and pasting the code for a custom dependency property and the associated property changed event handler stuff, but the primary reason for all of this generic code is to add a helpful level of abstraction that will result in much less copying and pasting and resulting errors.
The where constraints are fairly straight forward. You can attach an attached property only to a DependencyObject, thus we restrict type T to be a DependencyObject. The owner type must have a parameterless constructor so we restrict that type with the new() requirement. The type A is the type of the attached property and can be of any type.
The next part of this class looks much like the GenericAttachedProperty<> in my previous blogs.
static Type _type = typeof(O);
public T AssociatedObject { get; private set; }
public AttachedPropertyAssociatedObject()
{ }
public AttachedPropertyAssociatedObject(T associatedObject)
{ AssociatedObject = associatedObject;
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(A),
typeof(AttachedPropertyAssociatedObject<O, T, A>),
#if Silverlight
new FrameworkPropertyMetadata(false, OnValueChanged));
#else
new PropertyMetadata(new PropertyChangedCallback(OnValueChanged)));
#endif
public static A GetValue(DependencyObject d)
{ return (A)d.GetValue(ValueProperty);
}
public static void SetValue(DependencyObject d, A value)
{ d.SetValue(ValueProperty, value);
}
We have the strongly typed AssociatedObject property to hold onto the object that is being attached to and the strongly typed Value attached property that is the property that is being attached to the AssociatedObject. We also have constructors for an instance of this class. The _type variable is used to store the type of the owner type.
The next part is straight from the part 2 blog and is used to track the value changed event handlers.
public static void AddValueChangedHandler(DependencyObject sender, PropertyChangedCallback callback)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(sender);
if (propertyChangedCallbackDictionary == null)
{ propertyChangedCallbackDictionary = new Dictionary<Type, PropertyChangedCallback>();
sender.SetValue(GenericAttachedPropertyChangedCallbackDictionary.ValueProperty, propertyChangedCallbackDictionary);
}
if (propertyChangedCallbackDictionary.ContainsKey(_type))
{ propertyChangedCallbackDictionary[_type] += callback;
}
else
{ propertyChangedCallbackDictionary[_type] = callback;
}
}
public static void RemoveValueChangedHandler(DependencyObject sender, PropertyChangedCallback callback)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(sender);
if ((propertyChangedCallbackDictionary != null) &&
(propertyChangedCallbackDictionary.ContainsKey(_type)))
{ propertyChangedCallbackDictionary[_type] -= callback;
}
}
The next part contains the meat of this new class. This is where the instance of the attached property class is created and attached to the AssociatedObject. Remember that the OnValueChanged event handler is called anytime you’re attaching a value to the AssociatedObject.
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ T associatedObject = d as T;
if (associatedObject == null)
{ throw new Exception(String.Format("DependencyObject must be of type {0}", typeof(T))); }
O attachedPropertyAssociatedObject = GenericAttachedProperty<O>.GetValue(d);
if (e.NewValue != null)
{ if (attachedPropertyAssociatedObject == null)
{ attachedPropertyAssociatedObject = new O();
attachedPropertyAssociatedObject.AssociatedObject = associatedObject;
GenericAttachedProperty<O>.SetValue(associatedObject, attachedPropertyAssociatedObject);
attachedPropertyAssociatedObject.Initialize();
}
else
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(associatedObject);
if ((propertyChangedCallbackDictionary != null) &&
(propertyChangedCallbackDictionary.ContainsKey(_type)))
{ PropertyChangedCallback callback = propertyChangedCallbackDictionary[_type];
if (callback != null)
{ callback(associatedObject, e);
}
}
}
}
else
{ if (attachedPropertyAssociatedObject != null)
{ attachedPropertyAssociatedObject.UnInitialize();
GenericAttachedProperty<O>.SetValue(attachedPropertyAssociatedObject, null);
}
}
}
public virtual void Initialize()
{ }
public virtual void UnInitialize()
{ }
What we are doing here the first time through is creating an instance of this class, attaching it to the AssociatedObject, and then calling the virtual Initialize function. The initialize function is overridden in the O class and is where you implement the custom behavior you want. If you want to disable your custom behavior, you set the value of the attached property to null and the virtual UnInitialized function gets called. So in your O class you override that to undo the custom behavior. If you change the value of the attached property then you grab the change handler (if any) for your instance of this class and call it.
In my next blog, we’ll look at a simple implementation of this abstract class.