Wednesday, October 29, 2008

Building an Observable Model in Silverlight

Now that Silverlight 2 has been released .NET web developers can begin writing rich Internet applications in languages like C# and VB.NET which they know and love.  However if you have spent the last few years developing web applications using ASP.NET you may have developed certain habits that will not serve you well in when writing Rich Internet Applications.  Undoubtedly one of those habits is avoiding state.  Sure you could always use the view state or worse, the session state to store information across page loads.  However it's generally advisable to use as little state as possible to maximize the scalability of your ASP.NET applications.  It's important to unlearn this behavior now that Silverlight 2 has been released. 

Repeat after me: "State is good." 

Client state, this is.  Modern hardware is fast and memory is cheap.  Part of building rich Internet applications is leveraging all that juice to transform what users expect from web applications.

Of course this calls for a very different set of design patterns than the ones we use to build stateless applications.  In the RIA world it is no longer necessary, nor efficient, to destroy and recreate objects all the time. However if we keep objects around a long time we run the risk that they will get into inconsistent states.  An example of this problem is an application not updating the status bar and disabling the "Save" menu item when a new file is loaded in a text editor application.  The problem of synchronizing UI elements gets exponentially more complex as more UI elements are added.

One way of keeping long-lived view objects synchronized with each other is to design an observable model.  Many of your favorite MS applications (ex. Visual Studio, Office) use this model.  I'm not going to explain the observable model to you in detail because it has been explained very well by others already.  To summarize, in an observable model view objects subscribe to model objects and are responsible for updating themselves when the model changes.  It sounds simple but it is a very powerful way of managing complexity in UI's because you can add new types of view objects without touching the code in either the model or the other views.

In this blog post I want to show you how to create an observable model in Silverlight.  Let's use Silverlight Charts as an example, partly because it's a simple example of an observable model in Silverlight and partly because I just spent the last three months working on it with Delay (and others) so it's fresh in my mind.  

The Observable Model in Silverlight Charts

The Silverlight Chart has a collection of axis controls, series controls, and a legend control with a collection of UIElement's.  All of the elements in the various collections have to be inserted into the Chart's (or Legend's) visual tree.  The Chart (and Legend) needs to detect changes to their collections and add or remove the UIElements from visual tree accordingly.  Obviously the controls also need to be removed from the template parts when they are removed from the collection.  In this case the collections make up the model and the Chart and Legend's panel template parts are the views.

The Model

chart

The View

visualtree

Responding to Collection Changes

In the .NET framework there are several interfaces we have available to us to help us create an observable model.  You may be familiar with the INotifyCollectionChanged interface in the System.ComponentModel namespace.  It raises an event that informs listeners of what changes occurred in the collection.  There is also a handy object that implements it called ObservableCollection<T>.  The ObservableCollection is a good choice for our model collections.  It even implements IList so it can be populated in XAML like so:

<charting:Chart Title="Typical Use"> <charting:Chart.Series> <charting:BarSeries Title="Population" ItemsSource="{Binding PugetSound, Source={StaticResource City}}" IndependentValueBinding="{Binding Name}" DependentValueBinding="{Binding Population}"/> </charting:Chart.Series> </charting:Chart>

When the Series observable collection is changed the Chart responds by synchronizing the objects in the collection with those in the SeriesContainer template parts.

Responding to Property Changes

Although not shown above there is another template part behind the SeriesContainer called the GridLinesContainer.  This is where the grid lines control associated with a particular axis are inserted so that they appear behind the series.  The axis exposes the GridLines control it wants inserted into the GridLinesContainer via it's GridLines dependency property.  The Chart must detect changes to this property and ensure that the control exposed via this property is inserted into the GridLinesContainer template part.

You're probably already familiar with INotifyPropertyChanged.  Implementing this interface allows a object to signal to the UI that it has changed.  Although it might look like this would be the interface to use it is not appropriate in this case.  Since axis is a control and its GridLines property is a dependency property, a more appropriate way of exposing changes to it is to expose a DependencyPropertyChanged event of type DependencyPropertyChangedHandler<T>. 

There are three advantages to using DependencyPropertyChangedHandler:

1.  It is generic and is therefore statically typed.

2.  It provides both information about both the old and new value.  This makes it easy for the chart to find the old control and remove it. 

3.  It is a routed event which will provide more flexibility as Silverlight matures and incorporates more WPF features for handling routed events.

Overcoming Limitations

Synchronizing property values or responding to property changes is more challenging in Silverlight than in WPF.  There are a few limitations you should be aware of:

No Notifications on Custom Dependency Properties

Binding objects that bind to custom dependency properties (those that you create yourself vs. those that are native) are not able to detect when a property's value changes.  As a result if you bind two custom dependency properties together the bound property will only be set once regardless of how many times the source property is changed.  One way of overcoming this limitation is expose dependency property change events for the custom properties you would like to bind together and synchronize their values in the event handlers.

No Multi-Bindings

The solution to this problem is similar to the previous one.  Hook the property change events of several dependency properties and do your aggregation operations in code instead of inside of a value converter.

No Way of Getting a PropertyDescriptor for a Dependency Property

There's no way of getting a property descriptor for a dependency property in Silverlight.  As a result this wont work:

DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty (UIElement.VisibilityProperty, typeof(UIElement)); descriptor.AddValueChanged (this.labelShowHide, new EventHandler(VisibilityChanged));

So how do you listen for a change to a native dependency property?  One solution to this problem is to create a private attached dependency property in your class (assuming it inherits from DependencyObject), bind it to the native dependency property, and hook the PropertyChanged event of your attached property.  This will work:

 

public void ListenForChangeToProperty(string propertyName, UIElement element) { Binding b = new Binding(propertyName) { Source = element }; element.SetValue(HiddenAttachedProperty, b); } private static readonly System.Windows.DependencyProperty HiddenAttachedProperty = System.Windows.DependencyProperty.RegisterAttached( "HiddenAttached", typeof(double), typeof(MyClass), new System.Windows.PropertyMetadata(OnHiddenAttachedPropertyChanged)); private static void OnHiddenAttachedPropertyChanged(System.Windows.DependencyObject dependencyObject, System.Windows.DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; double value = (double) eventArgs.NewValue; // Respond to changes to property }

An Observable Model is a Beautiful Thing

It's like a guitar that is always in tune no matter how hard you thrash.  While the aforementioned limitations do make it very difficult to create observable models declaratively, Silverlight provides the necessary interfaces and delegates so that you don't need to reinvent the wheel.  Although you'll be writing more code in Silverlight than in WPF you'll still find that it's surprisingly easy.  Now that you have some tricks to help you overcome the known limitations you should embrace this pattern so that you can deliver the rich UI's your clients expect without spending your weekends tracking down bugs at the office.

13 comments:

Anonymous said...

It's very true, not only does Silverlight make state management much easier (actually, it becomes an almost none issue) it can also significantly reduce complexity and resource requirements for the server.

David Roh

Anonymous said...

Thank you for your interesting post. It's very disappointing that Silverlight's data binding model is so different to WPF.

I'm interested in the last part of your where you suggest that you can get property changed from Silverlight objects by creating your own DependencyProperties.

Unfortunately, I am unable to get this to work. Do you have a working example of this? I would have expected this code to have worked:
Binding b = new Binding("ActualWidth");
b.Source = LayoutRoot;
_TextBlock.SetBinding(TextBlock.TextProperty, b);

Do you have any ideas on how I might be able to get that to work?

...Stefan

Jafar Husain said...

At first glance it appears this should work. One gotcha to look out for is that in certain circumstances you must convert the data type using a converter. In this case the Text property expects a string and you are attempting to bind it to a double. Try creating a value converter that does nothing but call ToString on the double and see if that works for you.

Anonymous said...

Thanks for the suggestion. Unfortunately, my value converter only gets called once, when the binding is applied. After that it never gets called.

It just appears to me that the internal DependencyProperties never return changed references outside of the Silverlight core, because thus far I have not managed to get it to work with any properties including simple things like selected item in a ListBox.

It seems very odd to me because if you look at the binding expression code I would have expected to be notified.

Tom Keeler said...

stefan,
the silverlight TextBox has a bug where it only updates the binding when the control loses focus, unlike most if not all of the other silverlight controls, and you don't have the option to specify that the binding should update onpropertychanged. moreover your code snippet didn't specify a Mode. perhaps you meant to create a TwoWay binding?

Anonymous said...

Agh! Looks like it's just what I want to use to observe a base Children property...but getting an error when it tries to attach the Binding to the static HiddenProperty. (line 6 or so).
Says Binding is not a valid value for the property.

Any help would be very much appreciated! (I'm trying to get away from listening to *ChildPanel_LayoutUpdated, which is not working...)


public class VertexPanel : Panel {

public VertexPanel() {
ListenForChangeToProperty("Children", this);
}
public void ListenForChangeToProperty(string propertyName, UIElement element) {
System.Windows.Data.Binding b =
new System.Windows.Data.Binding(propertyName) { Source = element };
element.SetValue(HiddenAttachedProperty, b);
}
static readonly System.Windows.DependencyProperty HiddenAttachedProperty = System.Windows.DependencyProperty.RegisterAttached(
"HiddenAttached",
typeof(UIElementCollection),
typeof(Panel),
new System.Windows.PropertyMetadata(OnHiddenAttachedPropertyChanged)
);
static void OnHiddenAttachedPropertyChanged(System.Windows.DependencyObject dependencyObject, System.Windows.DependencyPropertyChangedEventArgs eventArgs) {
UIElement source = dependencyObject as UIElement;
UIElementCollection value = (UIElementCollection)eventArgs.NewValue;
// Respond to changes to property
//...
}
}

Again...thank you!

rex hansen said...

Hi guys,

I'm encountering the same issue as Stefan, the property changed callback method (e.g. OnHiddenAttachedPropertyChanged) is only being called once, when the binding is applied. It's never called again. Is there a solution for this?

Thanks
-Rex

Ted Howard said...

I get a "'System.Windows.Data.Binding' is not a valid value for property ..." unless I set the DP type to "typeof(object)". Looking at the code (thanks again Reflector), I can't see any way that a user-created DependencyProperty can ever accept an instance of Binding such that binding works.

Back to square one for getting notifications, I think.

Anonymous said...

The problem with INotifyPropertyChanged and all other nonsense is that it is SO, SO, SO INNEFICIENT AND BROKEN it is not the cure to anything updating and changing fast.

Large number of applications slow down to an unusable perf penalty because it passes way, way, way too much traffic and junk of a poorly designed Java-style 'binding' mechanisms

Anonymous said...

new polos men poloswomen polosdiscount polos
summer polospolo shirts whosalepolo fashionembroodered polos
tennis racketsclothing poloclothingedhardyshirt
edhardyclothingsummer ed hardy clothingcheap shirtsed hardy brand
cheap ed hardypolo shirts cheapcheap tennis racketsdiscount tennis rackets
ralphlaurenpoloshirtscheappolospolo fashionpolo logo
polo shirts in voguepolo women clothinged-hardy shirtsed-hardy sunglasses
ed-hardy logopolofashioncheaptennisracquetsed-hardy sunglasses
ed-hardy sunglassesed-hardy clothingdiscount polo shirtswholesale ed hardhy shirts
clothingfashionpolos summertennisrackets discountpolos clothes
wilson k sixedhardyclotheswholesale-polo-shirts

Anonymous said...

In preparation for the purchase of a tennis racquetbefore, we must consider your financial ability to bear; On this basis, a further comparison, as far as possible, choose your tennis racket. Now a lot of cheap tennis racquet and more mixed materials, the proportion of mixed-use to control the stiffness of the tennis racquet discount and the shock-absorbing capacity, the more rigid cheap tennis racket, the swing more powerful force; but the relative resilience of the shock-absorbing capacity and discount tennis racket performance of talks on the easier it is for the wrist and elbow injury.
head junior tennis racket
wilson tennis racquet
wilson tennis racket
head tennis racket
babolat tennis racket
Womens Handbags
Cheap Purses
Designer Handbags

Anonymous said...

Acer Laptop Batteries
Apple Laptop Batteries
Compaq Laptop Batteries
Dell Laptop Batteries
HP Laptop Batteries
IBM Laptop Batteries
Lenovo Laptop Batteries
Samsung Laptop Batteries
Sony Laptop Batteries
Toshiba Laptop Batteries
ASUS Laptop Batteries
Gateway Laptop Batteries
LG Laptop Batteries
NEC Laptop Batteries
HITACHI Laptop Batteries
Panasonic Laptop Batteries
BenQ Laptop Batteries
Fujitsu Laptop Batteries

Anonymous said...

Women’s nike tn Shox Rivalry est le modèle féminin le plus tendance de baskets pour le sport. tn chaussuresConcernant la semelle :Cheap Brand Jeans ShopMen Jeans - True Religion Jeans nike shoes & Puma Shoes Online- tn nike, le caoutchouc extérieur, l’EVA intermédiaire et le textile intérieur s’associent pour attribuer à la.ed hardy shirts pretty fitCharlestoncheap columbia jackets. turned a pair of double plays to do the trick.Lacoste Polo Shirts, , Burberry Polo Shirts.wholesale Lacoste polo shirts and cheap polo shirtswith great price.Thank you so much!!cheap polo shirts men'ssweate,gillette mach3 razor bladesfor men.As for

About Me

My photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.