Debugging Dependency Properties in WPF: Part 3
In my last couple posts, I've highlighted a way to inject debugging into dependency properties updated via data binding. It seems to work fine, but it's not clear how to apply it in certain scenarios. In our examples, we were attempting to debug a dependency property we defined on our own control. What if we want to debug an inherited property? It's not immediately obvious how to set property metadata in this case, but a little digging turns up some answers. Specifically, we can override default metadata:
1: static MyUserControl()
2: { 3: UserControl.WidthProperty.OverrideMetadata(typeof(MyUserControl),
4: new FrameworkPropertyMetadata(PrintfDebugger()));
5: }
And it works:
Here we use our control's static constructor to hook into the property changed callback. Notice that we're using FrameworkPropertyMetadata. I originally tried using the normal PropertyMetadata, but this resulted in a runtime exception. Apparently, you can only override metadata with the same type (or a derived type). Since WidthProperty uses FrameworkPropertyMetadata, so must we. Actually, I originally attempted to override metadata on ActualWidthProperty, but it turns out that this is impossible! ActualWidthProperty uses ReadOnlyFrameworkPropertyMetadata, which is an internal class. So we're out of luck if we want to get debug info on read-only properties.
Actually, there is something we can do. We can add a normal event handler to a dependency property descriptor like so:
1: public MyUserControl()
2: { 3: InitializeComponent();
4: DependencyPropertyDescriptor.FromProperty(UserControl.ActualWidthProperty,
5: typeof(MyUserControl)).AddValueChanged(this, (sender, e) =>
6: { 7:
8: });
9: }
Here we add an event handler that will fire whenever the value changes, allowing us to debug via normal means. This doesn't really fit in with our other solution, and we don't get nearly as much information as with the property changed callback, but I think it's the best we can do. Notice that we're using the regular constructor here and not the static one.
There's still one big scenario I've more or less ignored up to this point. Suppose you want to debug the properties of a child control that's not inherited at all? For example, suppose we host an ellipse in our user control like this:
1: <Grid>
2: <Ellipse Fill="Red" Height="50" Width="{Binding ActualWidth, ElementName=root}"/> 3: </Grid>
How can we debug that property? The Ellipse isn't our class, so it's not obvious how we can hook into its property's metadata. As it turns out, we can override the metadata on other classes:
1: static MyUserControl()
2: { 3: Ellipse.HeightProperty.OverrideMetadata(typeof(Ellipse),
4: new FrameworkPropertyMetadata(PrintfDebugger()));
5: }
This yields the desired result, hooking our debugger into the property updates:
Something doesn't feel quite right, though, does it? This ellipse already had some metadata, and we just stomped all over it. Our debugging is going to be pretty worthless if it produces any side effects. Unfortunately, ensuring the original metadata stays intact turns out to be much harder than you'd think.
1: static MyUserControl()
2: { 3: var metadata =
4: (FrameworkPropertyMetadata)Ellipse.HeightProperty.GetMetadata(typeof(Ellipse));
5: Ellipse.HeightProperty
6: .OverrideMetadata(typeof(Ellipse), GetDebugMetadata(metadata));
7: }
8:
9: public static FrameworkPropertyMetadata GetDebugMetadata(FrameworkPropertyMetadata metadata)
10: { 11: var flags = FrameworkPropertyMetadataOptions.None;
12: if (metadata.AffectsMeasure)
13: flags |= FrameworkPropertyMetadataOptions.AffectsMeasure;
14: if (metadata.AffectsArrange)
15: flags |= FrameworkPropertyMetadataOptions.AffectsArrange;
16: if (metadata.AffectsParentMeasure)
17: flags |= FrameworkPropertyMetadataOptions.AffectsParentMeasure;
18: if (metadata.AffectsParentArrange)
19: flags |= FrameworkPropertyMetadataOptions.AffectsParentArrange;
20: if (metadata.AffectsRender)
21: flags |= FrameworkPropertyMetadataOptions.AffectsRender;
22: if (metadata.Inherits)
23: flags |= FrameworkPropertyMetadataOptions.Inherits;
24: if (metadata.OverridesInheritanceBehavior)
25: flags |= FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior;
26: if (metadata.IsNotDataBindable)
27: flags |= FrameworkPropertyMetadataOptions.NotDataBindable;
28: if (metadata.BindsTwoWayByDefault)
29: flags |= FrameworkPropertyMetadataOptions.BindsTwoWayByDefault;
30: if (metadata.Journal)
31: flags |= FrameworkPropertyMetadataOptions.Journal;
32: if (metadata.SubPropertiesDoNotAffectRender)
33: flags |= FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender;
34:
35: return new FrameworkPropertyMetadata(metadata.DefaultValue, flags,
36: PrintfDebugger(metadata.PropertyChangedCallback), metadata.CoerceValueCallback,
37: metadata.IsAnimationProhibited, metadata.DefaultUpdateSourceTrigger);
38: }
Pretty ugly, huh? Sorry for the giant wall of code. Here we define another static method to build up a new piece of metadata that's identical save for the injected debugging. The biggest pain is that the flags aren't exposed publicly, so we have to reverse engineer them based on the exposed Boolean properties. We use the bitwise OR operator to build up the flags. (Incidentally, I didn't even realize "|=" was a valid operator in C#; I was pleasantly surprised when I tried it, and it worked.) The rest is pretty straightforward. We pass in all the info from the original metadata and return, preserving the original functionality while injecting our own debugging method.