INotifyPropertyChanged Dependency Properties for Silverlight PathTextBlock

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.

Advertisements

One Response to “INotifyPropertyChanged Dependency Properties for Silverlight PathTextBlock”

  1. Ken Smith Says:

    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”.


Comments are closed.

%d bloggers like this: