Debugging Dependency Properties in WPF with Property Changed Callbacks (or: How I Learned to Stop Worrying and Love printf Debugging)
Posted by: Clarity Blogs: ASP.NET,
on 18 Jan 2009 |
View original | Bookmarked: 0 time(s)
WPF is a great technology. It's changed the way we write rich desktop apps for Windows. But after using it for an appreciable length of time (eight or nine months), I've come to two conclusions:
1. Data binding sure is swell.
2. Data binding sure is a pain to debug.
I think this is the case with most powerful frameworks. You invoke a few lines of magic, and the tool takes care of all the low level wiring code for you. The problem comes when you treat this process like a black box; when you run into the edge cases where the abstraction starts to break down, it puts you in a tough spot when you try to track down the problem. So it is for debugging data binding with dependency properties in WPF. When a complex chain of bound data isn't working correctly, it can often be difficult to tell where the system is breaking down. To understand why this is, first we need to know a little bit about how dependency properties work in WPF. This is a pretty standard definition of a dependency property:
1:
public double MyProperty
2: { 3: get { return (double)GetValue(MyPropertyProperty); } 4: set { SetValue(MyPropertyProperty, value); } 5: }
6: public static readonly DependencyProperty MyPropertyProperty =
7: DependencyProperty.Register("MyProperty", typeof(double), typeof(MyUserControl));
There are a few things to note here. First, the regular .NET property (MyProperty) is simply a wrapper around the dependency property. The actual data will be associated with the dependency property defined just below. Also, take a look at the method calls inside the plain .NET property's get and set methods. These are used to store and retrieve information with our dependency property. More importantly, they will be called directly when our dependency property is updated via data binding. This brings us to the root of the problem: we can't debug dependency properties with our normal techniques, because they are updated with methods we have no insight into. There's nowhere to set a break point.
But there must be something we can do, right? There is. Let's take a closer look at the call to DependencyProperty.Register. This is where we're creating a static definition of the property to be used by instances of our class. You can see that we're currently passing three parameters: the name of the property, the type of the property, and the type of the owning property (i.e. our class). But there's also an optional fourth parameter: property metadata. Property metadata allows you to define some additional behaviors for your dependency property. Among these is the concept of a property changed callback; that is, a method that will run each time your dependency property is updated. This is commonly used for validation or to provide explicit updates to other properties in edge cases when data binding won't do, but we're going to use it to log changes to the console. Doing so is as simple as it sounds:
1: public static readonly DependencyProperty MyPropertyProperty =
2: DependencyProperty.Register("MyProperty", typeof(double), typeof(MyUserControl), 3: new PropertyMetadata((d, e) =>
4: { 5: Console.WriteLine("MyUserControl.MyProperty: {0}", e.NewValue); 6: }));
You can see here that we're passing in the additional property metadata, which in turn takes our property changed callback as a parameter. Here we define the callback inline via a lambda method. Our method body is extremely simple: we log a formatted message to the console (your output window, if you're debugging in Visual Studio). This should feel familiar to anyone who's done an appreciable amount of C/C++ development. Welcome back, printf debugging! For demo purposes, I have MyProperty bound to the actual width of the main window, like so:
1: <Grid>
2: <local:MyUserControl MyProperty="{Binding ActualWidth, ElementName=root}"/> 3: </Grid>
No magic here. When we run and resize the window . . .
We see the output as expected.
Is this good enough? Maybe for infrequent use, but it's kind of a pain. I know I wouldn't want to be littering my code with snippets like this, nor would I be very excited about manually editing that format string in every instance. I'm likely as not to forget to do it somewhere and end up with bad debugging info, and that's worse than no info at all. How can we make something more reusable? How about this:
1: public static readonly DependencyProperty MyPropertyProperty =
2: DependencyProperty.Register("MyProperty", typeof(double), typeof(MyUserControl), 3: new PropertyMetadata(PrintfDebugger));
4:
5: public static void PrintfDebugger(DependencyObject d, DependencyPropertyChangedEventArgs e)
6: { 7: Console.WriteLine("{0}.{1}: {2}", 8: d.DependencyObjectType.Name,
9: e.Property.Name,
10: e.NewValue);
11: }
Here we extract out our inline declaration into a reusable method. Note also that all class and property name information comes directly from the parameters themselves. If you try this out, you'll find that everything still works as expected. Astute readers will notice a minor problem with this approach. Our original solution was versatile in that it could fit inside any previously declared property changed callbacks. Here, with our debugging method in place, it seems like we can't run any other code when the dependency property changes.
There is an elegant solution, but it requires us to borrow an idea from functional programming: composition. Composition is the idea of using functions that take functions as parameters and return functions upon invocation; this is a popular technique in functional programming because it allows you to chain methods together in such a way as to easily inject new functionality. It's a perfect fit for our case. However, it's a little bit confusing, if you're not familiar with the concept, and my poor explanation probably doesn't help much. So let's jump into some code:
1: public static readonly DependencyProperty MyPropertyProperty =
2: DependencyProperty.Register("MyProperty", typeof(double), typeof(MyUserControl), 3: new PropertyMetadata(PrintfDebugger((d, e) =>
4: { 5: if ((double)e.NewValue > 700.0)
6: MessageBox.Show("wide load!"); 7: })));
8:
9: public static PropertyChangedCallback PrintfDebugger
10: (PropertyChangedCallback propertyChangedCallback)
11: { 12: return (d, e) =>
13: { 14: propertyChangedCallback(d, e);
15: Console.WriteLine("{0}.{1}: {2}", 16: d.DependencyObjectType.Name,
17: e.Property.Name,
18: e.NewValue);
19: };
20: }
This might be a lot to take in all at once, so let's break it down. Look at the PrintfDebugger method's signature. It takes a PropertyChangedCallback and returns one. It may seem a little odd, but it seems intuitively correct; if we're going to be chaining methods like this, both ends need to match up (hint: think of it like an extension cord). Moving on to the body, you can see that we're creating a PropertyChangedCallback inline and immediately returning it. Inside the body of our return function, you can see that we're invoking the actual callback function (passed to us as a parameter) and then logging out our debug info.
At this point, you may be feeling slightly uneasy about something you can't quite put your finger on. It's probably the invocation of propertyChangedCallback. We're not passing it in as a parameter to our inline function; instead, we're capturing a reference to it through a technique known as closure. Because the callback is in scope when our inline function is declared, it has access to it. And because it chooses to make use of it, the callback will remain in existence as long as our inline function exists. This is another powerful technique in functional programming that really makes the rest of it possible. We'd have a pretty tough time building up functions dynamically like this without closures.
Moving on to our dependency property, you can see that we're passing our own (trivial) callback to our PrintfDebugger method. Running the program, we see that everything works as we expect it to:
For completeness, let's add an overload to PrintfDebugger with no parameters:
1: public static readonly DependencyProperty MyPropertyProperty =
2: DependencyProperty.Register("MyProperty", typeof(double), typeof(MyUserControl), 3: new PropertyMetadata(PrintfDebugger()));
4:
5: public static PropertyChangedCallback PrintfDebugger()
6: { 7: return PrintfDebugger((d, e) => { }); 8: }
This simply calls the other method with an empty delegate as the parameter. It also allows me to highlight a crucial point: we are not passing PrintfDebugger as the parameter to PropertyMetadata. We're passing the invocation of PrintfDebugger. To put it another way, PropertyMetadata takes a PropertyChangedCallback as a parameter, and PrintfDebugger is not one. Rather, PrintfDebugger is a function that builds PropertyChangedCallbacks. Here's some code to clarify:
1: // this works: PrintfDebugger is invoked and returns a method
2: public static readonly DependencyProperty MyPropertyProperty =
3: DependencyProperty.Register("MyProperty", typeof(double), typeof(MyUserControl), 4: new PropertyMetadata(PrintfDebugger()));
5:
6: // this doesn't: we attempt to pass PrintfDebugger as a delegate
7: public static readonly DependencyProperty MyPropertyProperty =
8: DependencyProperty.Register("MyProperty", typeof(double), typeof(MyUserControl), 9: new PropertyMetadata(PrintfDebugger));
Anyway, I hope this helps some of you to ease the occasional pain WPF development can be.