In part 1 I talked about our strongly typed GenericAttachedProperty<A> class that allows you to quickly attach any object or class to a UI element. This works great as long as you only need to store one int and/or one string and/or one TMyBigClass on the same element. To be able to attach more than one of a particular type to the same UI element we need to update our GenericAttachedProperty<A> class. The problem is that our GenericAttachedProperty<A> class uses the same dependency property name “Value” for all generic types.
public class GenericAttachedProperty<T>
{ public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(T),
typeof(GenericAttachedProperty<T>),
null);
public static T GetValue(DependencyObject d)
{ return (T)d.GetValue(ValueProperty);
}
public static void SetValue(DependencyObject d, T value)
{ d.SetValue(ValueProperty, value);
}
}
If the types are different then you’re OK because the “Value” dependency property name is registered with the type of the parent. However, when you want to attach more than one of the same type of attached properties you can’t because you end up with the same “Value” dependency property and the same parent. Because you can’t have a static constructor, you can’t really pass in a different name for the attached property. The only option I could figure out to get around this problem was to make a new abstract type where you can pass in the owner type.
public abstract class GenericAttachedProperty<O, A>
{ public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(A),
typeof(GenericAttachedProperty<O, A>),
null);
public static A GetValue(DependencyObject d)
{ return (A)d.GetValue(ValueProperty);
}
public static void SetValue(DependencyObject d, A value)
{ d.SetValue(ValueProperty, value);
}
}
So now when we need to attach two ints (or any other object or class) to the same element you just need to declare new classes for each instance of the dependency property type you want to attach. Something like this:
class LeftCountAttachedProperty : GenericAttachedProperty<LeftCountAttachedProperty, int> { }; class RightCountAttachedProperty : GenericAttachedProperty<RightCountAttachedProperty, int> { }; This works because now when the “Value” dependency property is registered, it’s being registered to different parent types (which is defined in the third parameter in the RegisterAttached call). Now you can attach multiple unique int to any UI element by using its own class. Those calls would look something like this:
#region Unique Int
private void LeftCountAttachedProperty_Click(object sender, RoutedEventArgs e)
{ int count = LeftCountAttachedProperty.GetValue(textBlock1);
++count;
LeftCountAttachedProperty.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Left AttachedProperty: {0}", count.ToString()); }
private void RightCountAttachedProperty_Click(object sender, RoutedEventArgs e)
{ int count = RightCountAttachedProperty.GetValue(textBlock1);
++count;
RightCountAttachedProperty.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Right AttachedProperty: {0}", count.ToString()); }
#endregion
So what about the fourth parameter in the RegisterAttached call. Can we use the PropertyMetadata parameter to set and use a PropertyChangedCallback event handler for our GenericAttachedProperty? Yes, but it does require another updated abstract class.
If we just update the RegisterAttached call with the proper PropertyMetadata parameter to call a PropertyChangedCallback event handler like so:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(A),
typeof(GenericAttachedPropertyValueChanged<O, A>),
#if Silverlight
new FrameworkPropertyMetadata(false, OnValueChanged));
#else
new PropertyMetadata(new PropertyChangedCallback(OnValueChanged)));
#endif
Notice the #if to handle the differences between Silverlight and WPF.
The event handler OnValueChanged must be static and will be called upon a change to any attached property of the same type with the same parent type regardless of what UI element the attached property is attached. To handle this problem we must be able to store separate PropertyChangedCallback event handlers with the UI elements that you’re attaching properties to. First, we need to add a couple of static members to add and remove PropertyChangedCallback event handlers.
public static void AddValueChangedHandler(DependencyObject element, PropertyChangedCallback callback)
{ }
public static void RemoveValueChangedHandler(DependencyObject element, PropertyChangedCallback callback)
{ }
We could just attach the callback event handler to the element with a call like this:
GenericAttachedProperty<PropertyChangedCallback>.SetValue(element, callback);
But this would only allow you to ever attach one event handler to any one element regardless of the owner type. So if you tried to add a event handler to a UI element named textBox1 with the LeftCountAttachedProperty from above and then attached another event handler to the same UI element with RightCountAttachedProperty from above, you would overwrite the first event handler with the second one because the event handlers are the same type. What we need is to store the event handlers in a Dictionary that is keyed with the owner type. So we need a new class declaration to handle this.
class GenericAttachedPropertyChangedCallbackDictionary :
GenericAttachedProperty<GenericAttachedPropertyChangedCallbackDictionary, Dictionary<Type, PropertyChangedCallback>> { }
Now each UI element can maintain its own dictionary of event handler keyed with the owner type. Now we can fill in the Add/Remove members.
static Type _type = typeof(O);
public static void AddValueChangedHandler(DependencyObject element, PropertyChangedCallback callback)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(element);
if (propertyChangedCallbackDictionary == null)
{ propertyChangedCallbackDictionary = new Dictionary<Type, PropertyChangedCallback>();
element.SetValue(GenericAttachedPropertyChangedCallbackDictionary.ValueProperty, propertyChangedCallbackDictionary);
}
if (propertyChangedCallbackDictionary.ContainsKey(_type))
{ propertyChangedCallbackDictionary[_type] += callback;
}
else
{ propertyChangedCallbackDictionary[_type] = callback;
}
}
public static void RemoveValueChangedHandler(DependencyObject element, PropertyChangedCallback callback)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(element);
if ((propertyChangedCallbackDictionary != null) &&
(propertyChangedCallbackDictionary.ContainsKey(_type)))
{ propertyChangedCallbackDictionary[_type] -= callback;
}
}
The first thing we do with either call is retrieve the dictionary from the UI element. If the dictionary comes back null when adding an event handler, then it’s the first time we’re adding an event handler, and the dictionary must be created and attached to the element. We can then add the event handler to the dictionary with the owner type as a key.
Now we just need to fill in the static OnValueChanged event handler.
public static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(sender);
if ((propertyChangedCallbackDictionary != null) &&
(propertyChangedCallbackDictionary.ContainsKey(_type)))
{ PropertyChangedCallback callback = propertyChangedCallbackDictionary[_type];
if (callback != null)
{ callback(sender, e);
}
}
}
It’s a simple matter to retrieve the dictionary. Then the event handler keyed upon the owner type can be called.
The complete updated class is listed here.
class GenericAttachedPropertyChangedCallbackDictionary :
GenericAttachedProperty<GenericAttachedPropertyChangedCallbackDictionary, Dictionary<Type, PropertyChangedCallback>> { }
/// <summary>
/// A strongly typed generic attached property with property changed callback support
/// </summary>
/// <typeparam name="O">The type of the owner class</typeparam>
/// <typeparam name="A">Type of the attached property</typeparam>
public abstract class GenericAttachedPropertyValueChanged<O, A>
{ static Type _type = typeof(O);
public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(A),
typeof(GenericAttachedPropertyValueChanged<O, 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);
}
public static void AddValueChangedHandler(DependencyObject element, PropertyChangedCallback callback)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(element);
if (propertyChangedCallbackDictionary == null)
{ propertyChangedCallbackDictionary = new Dictionary<Type, PropertyChangedCallback>();
element.SetValue(GenericAttachedPropertyChangedCallbackDictionary.ValueProperty, propertyChangedCallbackDictionary);
}
if (propertyChangedCallbackDictionary.ContainsKey(_type))
{ propertyChangedCallbackDictionary[_type] += callback;
}
else
{ propertyChangedCallbackDictionary[_type] = callback;
}
}
public static void RemoveValueChangedHandler(DependencyObject element, PropertyChangedCallback callback)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(element);
if ((propertyChangedCallbackDictionary != null) &&
(propertyChangedCallbackDictionary.ContainsKey(_type)))
{ propertyChangedCallbackDictionary[_type] -= callback;
}
}
public static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{ Dictionary<Type, PropertyChangedCallback> propertyChangedCallbackDictionary =
GenericAttachedPropertyChangedCallbackDictionary.GetValue(sender);
if ((propertyChangedCallbackDictionary != null) &&
(propertyChangedCallbackDictionary.ContainsKey(_type)))
{ PropertyChangedCallback callback = propertyChangedCallbackDictionary[_type];
if (callback != null)
{ callback(sender, e);
}
}
}
}
To demonstrate the various generic classes, I’ve prepared a sample Silverlight application here.
All the attached properties in this demo are attached to the same UI element – textBlock1.
The buttons labeled “Left Int” and “Right Int” have the following click event handlers.
#region Basic Int Type
private void LeftIntCount_Click(object sender, RoutedEventArgs e)
{ int count = GenericAttachedProperty<int>.GetValue(textBlock1);
++count;
GenericAttachedProperty<int>.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Left Int: {0}", count.ToString()); }
private void RightIntCount_Click(object sender, RoutedEventArgs e)
{ int count = GenericAttachedProperty<int>.GetValue(textBlock1);
++count;
GenericAttachedProperty<int>.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Right Int: {0}", count.ToString()); }
#endregion
Clicking either button will first call GetValue of the generic class. The generic type we are looking for is an int, and we are looking for it in the textBlock1 element. The int value is incremented, and the new value is attached back to the textBlock1 element. The new value is then displayed on the textBlock1 element. By clicking both buttons, it becomes obvious that the same value is being read, incremented, and reattached regardless of whether the left or right button is clicked.
For the next part of the demo, we need to declare a couple of classes to handle two unique int attached properties.
class LeftCountAttachedProperty : GenericAttachedProperty<LeftCountAttachedProperty, int> { }; class RightCountAttachedProperty : GenericAttachedProperty<RightCountAttachedProperty, int> { };
The buttons labeled “Left Count” and “Right Count” have the following click event handlers:
#region Unique Int
private void LeftCountAttachedProperty_Click(object sender, RoutedEventArgs e)
{ int count = LeftCountAttachedProperty.GetValue(textBlock1);
++count;
LeftCountAttachedProperty.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Left AttachedProperty: {0}", count.ToString()); }
private void RightCountAttachedProperty_Click(object sender, RoutedEventArgs e)
{ int count = RightCountAttachedProperty.GetValue(textBlock1);
++count;
RightCountAttachedProperty.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Right AttachedProperty: {0}", count.ToString()); }
#endregion
Same routine as before except that we are using our unique classes to store unique int values. Clicking either button will demonstrate that the values are indeed unique.
For the last part of the demo, we need to declare our unique callback classes.
class LeftCountAttachedCallbackProperty : GenericAttachedPropertyValueChanged<LeftCountAttachedProperty, int> { }; class RightCountAttachedCallbackProperty : GenericAttachedPropertyValueChanged<RightCountAttachedProperty, int> { };
The event handlers for the CheckBox, the unique PropertyChangedCallbacks, and the buttons labeled “Left Count Callback” and “Right Count Callback” are as follows.
#region Unique Int with Callback
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{ LeftCountAttachedCallbackProperty.AddValueChangedHandler(textBlock1, OnLeftValueChanged);
RightCountAttachedCallbackProperty.AddValueChangedHandler(textBlock1, OnRightValueChanged);
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{ LeftCountAttachedCallbackProperty.RemoveValueChangedHandler(textBlock1, OnLeftValueChanged);
RightCountAttachedCallbackProperty.RemoveValueChangedHandler(textBlock1, OnRightValueChanged);
leftTextBlock.Text = string.Empty;
rightTextBlock.Text = string.Empty;
}
private void LeftCountAttachedPropertyCallback_Click(object sender, RoutedEventArgs e)
{ int count = LeftCountAttachedCallbackProperty.GetValue(textBlock1);
++count;
LeftCountAttachedCallbackProperty.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Left Callback: {0}", count.ToString()); }
private void RightCountAttachedPropertyCallback_Click(object sender, RoutedEventArgs e)
{ int count = RightCountAttachedCallbackProperty.GetValue(textBlock1);
++count;
RightCountAttachedCallbackProperty.SetValue(textBlock1, count);
textBlock1.Text = String.Format("Right Callback: {0}", count.ToString()); }
void OnLeftValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ leftTextBlock.Text = e.NewValue.ToString();
}
void OnRightValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ rightTextBlock.Text = e.NewValue.ToString();
}
#endregion
The PropertyChangedCallback event handlers are set and unset by the CheckBox. Clicking the left and right buttons demonstrates that the values are unique and the event handlers are unique.
It’s important to remember that you can attach any class or object type to any UI element. Using these generic classes you can quickly attach whatever you may need to UI elements and even use the PropertyChangedCallback to those attached properties however you need.
I’ve included the generic classes into a solution that contains a Silverlight project and WPF project that share the same source code as well as the demo solution here.