Simulating IsSynchronizedWithCurrentItem in Silverlight (part 2)

.NET, Blend, Silverlight, Technical stuff, Work, WPF
See comments

This is part 2 of a two posts series about the property IsSynchronizedWithCurrentItem. In the previous post, we saw what it does in Windows Presentation Foundation. In this post, we will see that this property is missing in Silverlight, and propose a way to simulate it.

Like we mentioned in the previous post, the property IsSynchronizedWithCurrentItem is very handy to keep a List-Details view in synchronization. However, this handy property is not implemented in Silverlight 2. When the team at Microsoft implemented this new technology, they made it a subset of WPF, and had to decide which feature to keep and which to let go. This was the only way to get the framework to fit in less than 5 MB, which is a critical size for a plug-in distributed over the internet.

Note: In fact it’s more than just a missing property. The whole CollectionViewSource class is also missing altogether. We saw in the previous post that this class is the one that enables filtering, sorting and also selecting an item on a Collection.

The sample for this article can be downloaded here.

Binding to SelectedItem

One way to compensate for the missing property is to bind the property SelectedItem of the Selector class (of which ListBox, ComboBox and others derive) to a new property on the ViewModel.

However, this poses two problems:

  • The syntax in the XAML will be quite different from the WPF syntax. This is not a huge deal, but I would prefer to keep the Silverlight XAML as close to the WPF one as possible.
  • In the WPF sample we saw in the previous post, we provided a way to easily switch the synchronization on and off on the ComboBox. If we use the SelectedItem, however, switching it off will be more difficult, and will require some code behind.

So let’s go back to the drawing board and see if we can provide a better way to do that. We will do that using something called an Attached Behavior.

Attached Behavior, you say?

An attached behavior is actually an attached property which is not used for data, but to create a behavior in the targeted element. In the words of Josh Smith: “Attaching a behavior to an object simply means making the object do something that it would not do on its own“.

Attached properties are a very handy way to extend an element’s features without modifying the element itself. They can be compared to extension methods in C#. This is what you use when you say Grid.Column=”1″ in a XAML element. Since the element will not always be placed in a Grid, why should all elements have a “Column” property? Instead, the Column property is implemented on the Grid class, and attached to the element.

Expanding on that concept, we can use an attached property to attach a feature to an element, without modifying that element. For example, you could attach events to the element in the background, without the user being really aware of that, or create bindings.

The SelectorExtension class

The attached property “IsSynchronizedWithCurrentItemProperty” is declared on a new class named SelectorExtension (see the code in the sample here). The following points are worth mentioning:

  • Since in XAML, properties can be set in any order, we must account for the fact that the ItemsSource property of the Selector might not be set yet when the IsSynchronizedWithCurrentItem property is set. To resolve this issue, we resort to a little trick: We attach an event handler to the SelectionChanged event of each control that uses the property. Whenever the event is called, we know for sure that the ItemsSource has been set, and we can replace the event by a proper data binding. In fact, we create the binding on all the Selector controls that use this collection, and remove the event handlers. A Binding is preferable to an event handler, because it creates a weaker link between the elements, and because it is easy to create a TwoWay binding.

Note: Creating an event handler on controls in the background might cause a memory leak, if the control is deleted without the event handler being removed. This is one problem with this approach. When using this extension property, the property IsSynchronizedWithCurrentItem should be set to false before deleting the control. Setting this property to false will remove the event handler, and no leak will be caused.

  • We use a boolean flag in the SelectionChanged event handler. This is needed, because when one control’s event is fired, we modify the selection in all other controls using the same ItemsSource collection. This can cause an endless loop. By setting the flag to true, we just notify the application that we are currently processing the selections, and that the original event should just be allowed to go on.
  • If the property IsSynchronizedWithCurrentItem is set to false, we must remove the binding for the current control. This is needed to reproduce the exact same functionality as in WPF. Removing a Binding in Silverlight is not that easy, but you can simple create an empty binding on the SelectedItem property of the Selector control.

The ObservableCollectionEx class

The SelectorExtension we described above creates a binding in the background between the SelectedItem of the Selector control, and a corresponding property on its data source. In WPF, this is where the CollectionViewSource class comes in play in the background. However, we mentioned already that this class doesn’t exist in Silverlight. We will simulate this feature by extending the existing ObservableCollection class, and creating a new class called ObservableCollectionEx. This class is also a generic class, and derives directly from ObservableCollection. It features a new property named SelectedItem (of type T, to allow generic usage).

Because the class is a generic class, it is difficult to handle it in the SelectorExtension class, because we don’t know the type of the items it contains. Since we cannot make the SelectorExtension class a generic class itself (since we use it in XAML), the easiest way I found is to declare an interface named IObservableCollectionEx, with one setter property of type “object”, named “SelectedObject”. The class ObservableCollectionEx implements this interface, and the only action that SelectedObject does is set the SelectedItem. This allows us to work with this class in a non-generic way. For convenience, however, the SelectedItem is strongly typed, and the SelectedObject is not used either for binding, or for any other purpose. If someone has a better suggestion, I would be happy to hear it ;)

The new DataItems class

To recreate the functionality we wish to achieve, the Selector control must use an ObservableCollectionEx instead of an ObservableCollection. Since we are using the DataItems class, which is itself deriving from ObservableCollection<DataItem>, it is easy to replace this class in the Silverlight project and make it derive from ObservableCollectionEx<DataItem> instead.

This way, we can share the MainViewModel, which offers a property of type DataItems. The DataItems class in Silverlight will be different from the DataItems class in WPF, but this is hidden from the MainViewModel, and from the developer.

Sharing classes

In order to simplify the maintenance for classes that are exactly the same between WPF and Silverlight (such as the DataItem class and the MainViewModel class), you can share these classes between the 2 projects. It is a well known limitation of Silverlight that it cannot use DLLs compiled for WPF. However, the source code can be shared between both project types. This way, when one file is modified in WPF, the modifications will automatically be reflected in Silverlight. You will still have to build the Silverlight project separately (and we hope that this limitation will disappear some time in the future), but at least you have less source code files to maintain.

To add a shared source code file, right click on the Silverlight project (or on a subfolder) and select “Add / Existing item”. Then select the source code file you want to add. Instead of clicking on the button “Add”, click on the small arrow on this button, and select “Add As Link”.

image

Adding a source code file as Link

Testing the application

If you run the application sample, you will reproduce the exact same behavior than displayed in the previous post about WPF. The goal is reached, and the syntax is almost the same in WPF and in Silverlight.

In WPF:

<ListBox ItemTemplate="{StaticResource DataTemplate1}"
         ItemsSource="{Binding Items}"
         IsSynchronizedWithCurrentItem="True" />

<ComboBox IsSynchronizedWithCurrentItem="{Binding PropertyOn}"
          ItemTemplate="{StaticResource DataTemplate1}"
          ItemsSource="{Binding Items}" />

<TextBox Text="{Binding Items/TestName, Mode=TwoWay}"
         Height="30"
         Margin="0,10,0,0" />

In Silverlight:

<ListBox ItemTemplate="{StaticResource DataTemplate1}"
         ItemsSource="{Binding Items}"
         ex:SelectorExtension.IsSynchronizedWithCurrentItem="True" />

<ComboBox ex:SelectorExtension.IsSynchronizedWithCurrentItem="{Binding PropertyOn}"
          ItemTemplate="{StaticResource DataTemplate1}"
          ItemsSource="{Binding Items}" />

<TextBox Text="{Binding Items.SelectedItem.TestName, Mode=TwoWay}"
         Height="30"
         Margin="0,10,0,0" />

image

image

Previous entry | Next blog entry

Comments for Simulating IsSynchronizedWithCurrentItem in Silverlight (part 2)