My brother wrote a slick lens panel control with Silverlight 2 recently. He modeled it on the look and behaviour of the Mac OSX Leopard dock. It was so slick, I had to write my own implementation. You can check it out here. After I got it up and running we decided to do a kind of distributed (by about 800 miles) pair programming session to see if we could come up with cleaner ways to structure our lens panel controls.
I'm not going to go into the low-level implementation details in this article. The behaviour of the control is pretty much the same as the Mac dock control. Kevin has described the high-level approach we ended up with on his own blog, but I will highlight some features of XAML we used to make the lens panel controls more easily extendable.
When we finished refactoring our code, both of our lens panel controls ended up making use of the ItemsControl, data templates, data binding and dependency injection though XAML, but neither of our controls started out that way.
Dependency Injection
The first structural decision I made when I got far enough in to the code to start displaying items was to try to separate the source of the panel items from the lens panel control itself. The most basic way to do this is have a base class with the generic lens panel behaviour baked in, and then maybe derive versions for showing lens panel items from various different sources like Flickr, iStockPhoto, or custom menu items.
While this approach works, it tightly couples the base lens panel class with all other derived lens panels. What if you wanted to further customise the lens panel to display the individual items differently? You could derive some more classes with the new visuals baked in but again, this tightly couples even more classes together.
My first solution to this problem was to separate out each of the responsibilities into their own classes. The LensPanel class contained the logic to arrange and display the panel items. The LensPanel had a public property of type IPanelItemProvider. This allowed me to implement any number of concrete providers without directly coupling them to the lens panel control itself.
In addition to this, the providers contained a public property of type IPanelItemVisualFactory. This allowed me to further decouple the visual aspects of the panel items from the provider. All of these dependencies can be injected directly using XAML. Below is roughly how my first solution looked.
<LensPanel:LensPanel Width="1000" Height="300" NumberOfItems="12" >
<LensPanel:LensPanel.PanelItemProvider>
<LensPanel:iStockPanelItemProvider>
<LensPanel:iStockPanelItemProvider.PanelItemVisualFactory>
<LensPanel:ReflectionPanelItemVisualFactory />
</LensPanel:iStockPanelItemProvider.PanelItemVisualFactory>
</LensPanel:iStockPanelItemProvider>
</LensPanel:LensPanel.PanelItemProvider>
</LensPanel:LensPanel>
The above solution has some nice benefits to offer. First, it makes use of the Single Responsibility Principle (SRP). Each of the objects has a single clear responsibility. The lens panel only lays out panel items. The panel item provider only provides panel items. The panel item visual factory only creates panel item visuals. Second, it also makes use of the Open/Closed Principle (OCP). This means that the solution is open for extension (by creating new concrete provider and factory objects) but closed for modification (providing new concrete providers and factories requires no modification of the base control). Third, this solution is very unit testable.
But it turns out there's a much simpler way to implement the same separation of concerns by exploiting several features already built-in to Silverlight 2.
Data Binding And Templates
Rather than creating the provider interface, IPanelItemProvider, and then managing the connection of the items from the provider with the items in the lens panel, we decided to exploit Silverlight's data binding support. Both of our controls derive from the Panel class and it wasn't immediately obvious how we were going to bind directly to an items list within the panel. Kevin discovered the ItemsControl which makes this very simple.
The ItemsControl allows you to create a panel control where you can specify a panel template for the type of panel you want, in addition to a data template for the individual panel items. With only a small amount of refactoring, and without losing any of the benefits of our earlier approaches, we had reworked our solutions to use the ItemsControl. Below you can see I'm creating an ItemsControl and specifying my custom LensPanel class as the panel template and my custom LensPanelItem class for the item template.
<ItemsControl x:Name="_lensPanelControl" ItemsSource="{StaticResource iStockPhotoProvider}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<l:LensPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<l:LensPanelItem />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In addition to specifying the templates for the panel and panel items, I'm also binding the ItemsControl to an instance of the iStockPhotoProvider declared in the local resources. I chose to derive the iStockPhotoProvider directly from ObservableCollection so that once it is bound to the ItemsControl, any modifications to it result in an update of the ItemsControl. Again, this provides the same benefits as before. The provider is loosely coupled to the lens panel control. Below is the declaration for the concrete photo provider.
<UserControl.Resources>
<l:iStockPhotoProvider x:Key="iStockPhotoProvider" />
</UserControl.Resources>
This makes it possible for a designer to swap out the provider with different providers by simply modifying the XAML. No code changes are required.
Conclusion
So far, I've been very impressed with WPF and Silverlight 2. It's very obvious that the features of these frameworks are based on solid practical foundations. I found Chris Anderson's book Essential Windows Presentation Foundation (WPF) (Microsoft .NET Development Series)
to be an excellent guide to exploring how to use these features in practical ways. Also, he describes at a low level (and in very simple terms) the motivation behind some of these features and exposes details about their implementation. This was a big help in transitioning from a Windows Forms way of thinking to the more modern approach suggested by WPF and Silverlight 2.