We all know that when you create a DependencyProperty you get change notification for free (amongst other things), which is great when you are the owner of the property because you can setup change event handlers (which I have posted about in the past). You can even override the metadata for a DependencyProperty when you inherit from a class that owns a property you want notification for; injecting your change event handler. But, what about the time when you do own the property directly or via inheritance, how do you get change notification then?
The answer is to use a PropertyDescriptor - this is the class that Visual Studio and similar tooling would use to keep track of property values in the designer etc. - this is nice feature that you can exploit in your code. Given the property descriptors primary purpose it's not that surprising to find it in the System.ComponentModel namespace. Note that a property descriptor has been written specifically for dependency properties, to give additional information about the property, therefore to get a reference to one is as simple as:
DependencyPropertyDescriptor descriptor =
DependencyPropertyDescriptor.FromProperty
(UIElement.VisibilityProperty, typeof(UIElement));
descriptor.AddValueChanged
(this.labelShowHide, new EventHandler(VisibilityChanged));
This is actually only two lines of code, though it may look more. First we get a reference to the dependency property that we care about (VisibilityProperty in this instance) and then add an event handler to the ValueChanged event for a DependencyObject instance (a TextBlock called labelShowHide in this example) as well as supplying the event handler we would like to be invoked when the value has changed.
Lovely. However, now for the bad news, there are two issues with this implementation that may not be obvious right away. Firstly, notice that the event handler is only a simple EventHandler, therefore the event arguments will only be an EventArgs instance; therefore the rich change notification supplied by the "owner" scenarios, with old and new values being supplied with the event, are not available to you with this approach. Secondly, this implementation has a leak, a memory leak. The reference to your callback forever roots your class, as the reference is stored in a static Hashtable (# dun, dun, daahhhh! #)
There are a number of ways to deal with these issues depending on your scenario. In most cases I would suggest that you can safely ignore both.
However, if you *really* want to plug the leak then take a look at this great article by Andrew Smith; however, in all but the most complex cases I would suggest that when you no longer care about the notification for the target property it will also be right about the time your application is about to close; at which point everything will be reclaimed anyway.
To solve the change notification you could simply have a dictionary, keyed on the target object instance, storing the last known value for the target property. In all but a few edge cases you'll know the controls that you're watching, so this solution is simple and easy to implement:
// Define an internal dictionary to store the values
private Dictionary
new Dictionary
public MainWindow()
{
this.InitializeComponent();
// Descriptor code goes here...
// Set the initial values in the constructor.
this._currentValues.Add
(this.labelShowHide, this.labelShowHide.Visibility);
}
// Change Event Handler
private void VisibilityChanged(object sender, EventArgs e)
{
// Extract the values.
object oldValue = this._currentValues[sender];
object newValue = this.labelShowHide.Visibility;
// Do something with the data.
MessageBox.Show("Value changed from \"" +
oldValue.ToString() + "\" to \"" +
newValue.ToString() + "\"");
// Update the values list with the new value.
this._currentValues[sender] = newValue;
}
All we have here is a simple dictionary to store the current value for control, so we have it cached for when it changes, and a simple little piece of boiler plate in the event handler to extract the old value and update the dictionary with the new value. Obviously if you're monitoring many controls in this way you could easily factor out the boiler plate to a generic method or add another level of indirection by raising your own property changed event using the DependencyPropertyChangedEventArgs. Also, if you want to monitor many properties on the same control you'll have to invent another key.
So in summary, the simplest way to hook into the change notification for a DependencyProperty you do not or cannot own in some way is to use the PropertyDescriptor mechanism. In most cases the amount of code required to deal with the memory leak is not worth the effort (but be aware of it) and if you really need the old/new value functionality simply create a dictionary to store the old values for you.
I hope this helps.
No comments:
Post a Comment