I’ve had my eye on Silverlight for a while, but haven’t had much reason to do anything with it. However, I recently stumbled on Bill Reiss‘s PathTextBlock project (via Tim Heuer) and thought I’d try to help out as an excuse to dabble. In addition to implementing a naive 3D projection transform, I wanted to add support for bindable dependency properties. It was harder than it should have been, but the eventual solution turned out to be pretty elegant so I thought I’d share it here.
Defining a DependencyProperty
If you’re not familiar, the general stub for a DependencyProperty
in MyClass
looks something like this:
public static readonly DependencyProperty MyStringProperty = DependencyProperty.Register( "MyString", typeof(string), typeof(MyClass), new PropertyMetadata("My Default Value", MyClass.OnPropertyChanged)); public string MyString { get { return (string)GetValue(MyStringProperty); } set { SetValue(MyStringProperty, value); } } protected static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ... }
For more information on dependency properties, check out this article on MSDN. Note that the "MyString"
parameter is the name of the property and that DependencyProperty
has a public Name
property (in .NET 3.0).
Implementing INotifyPropertyChanged
Now that MyString
is a DependencyProperty
, we just need to implement INotifyPropertyChanged
on MyClass
. In WPF, that could look something like this:
public event PropertyChangedEventHandler PropertyChanged; protected static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var m = d as MyClass; if (m != null) { DependencyProperty p = e.Property; if (pcdo.PropertyChanged != null) pcdo.PropertyChanged(p, new PropertyChangedEventArgs(p.Name)); } }
Unfortunately, the .NET 3.0 implementation wasn’t good enough for Silverlight. In the Silverlight version of DependencyProperty
, the Name
property is nowhere to be found! It’s actually still there, it’s just internal for some reason. Since we can’t retrieve the name from the property object, I use a dictionary to cache the names instead. At the same time, let’s define a few helper methods to hide the dictionary:
private static Dictionary<DependencyProperty, string> props = new Dictionary<DependencyProperty, string>(); protected static DependencyProperty Register( string name, Type propertyType, Type ownerType, object defaultValue) { return Register(name, propertyType, ownerType, defaultValue, OnPropertyChanged); } protected static DependencyProperty Register( string name, Type propertyType, Type ownerType, object defaultValue, PropertyChangedCallback callback) { DependencyProperty prop = DependencyProperty.Register( name, propertyType, ownerType, new PropertyMetadata(defaultValue, callback)); props.Add(prop, name); return prop; } protected static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var m = d as MyClass; if (m != null) { DependencyProperty p = e.Property; string propertyName = props.ContainsKey(p) ? props[p] : "Unknown"; if (m.PropertyChanged != null) m.PropertyChanged(p, new PropertyChangedEventArgs(propertyName)); } }
And we can use our new Register method to initialize our properties:
public static readonly DependencyProperty MyStringProperty = Register("MyString", typeof(string), typeof(MyClass), "My Default Value"); public static readonly DependencyProperty MyBoolProperty = Register("MyBool", typeof(bool), typeof(MyClass), true);
<Rant>
I really wish MS would lay off the internal
access modifier. This case was pretty easy to work around, but there are other instances where it’s a significant barrier. For example, the PathTextBlock project defines its own Transform
and TransformGroup
classes that are functionally identical to those provided by the SL-FCL. The FCL’s GeneralTransform
and Transform
classes are even public…too bad their only constructor is internal!! So rather than build on the existing framework, the wheel gets reinvented. Grrr!
</Rant>
Frustrations aside, the solution works really well for data-bindable dependency properties. For PathTextBlock, I built the above code into a generic PropertyChangedDependencyObject
class from which to inherit. One thing to note: if a subclass hides the base OnPropertyChanged
method, like I do in Transform
, the property registration needs a reference to the new OnPropertyChanged
. Feel free to check out the latest source on CodePlex for the full working sample.
September 29, 2009 at 12:20 pm
Totally agree about the internal modifier. If there are genuine reasons for its use, I haven’t run into them yet. The vast majority of times that MS defaults to “Internal” in the code that it generates, it should really be “Public”.