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
The View
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.



