Fun with Attached Properties – Bindable RichTextBox.Xaml
Occasionally I’ll run into something in Silverlight that doesn’t behave how I want or expect it to.
What I’ve recently run into was trying to have a clean MVVM implementation that involved a RichTextBox and rich text content (in XAML markup format) being delivered to a Silverlight 4 client via a .NET RIA service. Sounds easy enough: I coded up my view model to have a string property that would contain the XAML from the server, set up my bindings like so:
Run the project and voila! Error!
What’s going on? Unfortunately when I ran into this, I poked and prodded and due to the completely worthless error message, I wasted at least 45 minutes trying to figure out what this error meant. Finally I decided to look at the documentation and realized it’s missing one key bit of text in the Remarks section: Dependency property.
That’s it, it’s not a dependency property so I can’t data bind into it. Mystery solved, now what do I do? I know a lot of people who would advocate subclassing RichTextBox and coding up a dependency property on it that wraps the Xaml property. When I was learning about WPF and Silverlight one of the biggest bits that kept sticking out was that it seemed like composition was really the preferred approach to extensibility as opposed to the WinForms/Java/ASP.NET method of subclassing controls to get them to behave how you need.
So, assuming I want to compose behavior instead of subclass, how do I do it? I use an attached property. An attached property is a dependency property, except it can be attached to an arbitrary DependencyObject-derived Type. It doesn’t have to be a property directly on the Type I want to use, so I don’t need to subclass.
Before I delve into my attached property, let’s look at two everyone should know: Grid.Column and Grid.Row. These two properties are attached properties because you don’t set them directly on the relevant Grid, you set it on the Grid’s children. In essence, you attach the properties to the Grid’s children (hence attached properties). TextBlock doesn’t know what Grid.Column is or what to do with it once it’s set, all it knows is you gave it a value for Grid.Column. When your TextBlock is inside a Grid, the Grid control knows to look for a Grid.Column and Grid.Row property to figure out where to position the TextBlock.
What I want to do is have a way to attach a bindable property to RichTextBox so that I can data bind my string property to it. Visual Studio has a snippet for this: “propa”. Type propa (as in “property attached”) into Visual Studio and hit tab twice and you get nearly all the code written for you:
All you need to do is fill in the blanks. What I want is a string property, the owner Type should be the name of the class I’m defining the attached property in and I can simply provide a default value of null. The only caveat here is the propa snippet is defined for WPF, so you have to change UIPropertyMetadata to just PropertyMetadata (because UIPropertyMetadata isn’t available in Silverlight). Also, to ensure that this only applies to RichTextBox, the Get and Set methods need DependencyObject changed to RichTextBox (this is how the XAML intellisense know what is and isn’t valid for any given element type).
How does this make the Xaml property bindable? It doesn’t! The key here is to provide a PropertyChangedCallback to the DependencyProperty.RegisterAttached method:
All I do here is every time the BindableXaml property changes is update the Xaml property with the new value (or null if it’s an empty or whitespace string). To use, just attach the property to the RichTextBox in my earlier example:
And now I have a behavior on RichTextBox that didn’t exist in the framework, using composition instead of inheritance, all with an attached property.The benefit here is you can compose multiple behaviors into a single object without needing to build up the various subclasses to do it.
This is about as simple as they get, I’ll have more complex examples of attached behaviors in another post.