* A more complete solution is available in this blog post
It contains code, examples and a NuGet package
Well, again a story about having a problem, searching for a solution and finally sharing it
So, this time it’s about trying to marry the RadTabControl from Telerik’s control set with Caliburn.Micro. I’m evaluating Telerik for our pilot project in Silverlight since I would like to speed up our development process in few areas. And then we’re using happily Caliburn.Micro in it’s Silverlight flavor.
Scenario: a RadTabControl should contain multiple individual TabItems composed in runtime and backed each by a View Model. And it should be completely ViewModel driven.
Let’s start with ViewModel, as a central piece of our code:
public class MainPageViewModel : Conductor<IScreen>.Collection.OneActive { private readonly FirstTabItemViewModel first; private readonly SecondTabItemViewModel second; public MainPageViewModel(FirstTabItemViewModel first, SecondTabItemViewModel second) { this.first = first; this.second = second; } protected override void OnInitialize() { base.OnInitialize(); Items.Add(first); Items.Add(second); ActivateItem(first); } }
Pretty straightforward.
Now, we have to wrap this model in a view. In better traditions of Caliburn.Micro it’s extremely succinct and simple:
<telerik:RadTabControl x:Name="Items"> <telerik:RadTabControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding DisplayName}" /> </DataTemplate> </telerik:RadTabControl.ItemTemplate> </telerik:RadTabControl>
It looks nice, but it actually doesn’t work. Yet… Caliburn.Micro is handling this way the TabControl from WPF but that of Silverlight is horribly broken (and MS isn’t in hurry to fix it). To have it work nicely with Telerik’s Silverlight RadTabControl we have to add another convention to Caliburn to hint how he should be dealing when doing bindings for RadTabControl. The code is very similar to binding conventions for WPF’s TabControl, I’ve simply adapted it to handle new case.
We will be adding new convention and we should do that in our override of Configure method in our Bootstrapper. The code:
ConventionManager .AddElementConvention<RadTabControl>(RadTabControl.ItemsSourceProperty, "ItemsSource", "SelectionChanged") .ApplyBinding = (viewModelType, path, property, element, convention) => { if (!ConventionManager.SetBinding(viewModelType, path, property, element, convention)) return false; var tabControl = (RadTabControl)element; if (tabControl.ContentTemplate == null && tabControl.ContentTemplateSelector == null && property.PropertyType.IsGenericType) { var itemType = property.PropertyType.GetGenericArguments().First(); if (!itemType.IsValueType && !typeof(string).IsAssignableFrom(itemType)) tabControl.ContentTemplate = ConventionManager.DefaultItemTemplate; } ConventionManager.ConfigureSelectedItem(element, RadTabControl.SelectedItemProperty, viewModelType, path); if (string.IsNullOrEmpty(tabControl.DisplayMemberPath)) ConventionManager.ApplyHeaderTemplate(tabControl, RadTabControl.ItemTemplateProperty, viewModelType); return true; };
And it works.
I’ve posted complete source code with my solution on GitHub, RadTabControlAndCaliburn.
Update 14/04/2011: Updated convention code to work with latest Caliburn.Micro 1.0 RTW.
This blog is about things I'm passionated - Software Development, User Experience, gadgets and few other facets of IT that are keeping me busy at work as well as fueling my knowledge and self-improvement demons.
#1 by Peter Zeckendorf on December 23, 2010 - 12:44
Thanks, this was the exact problem I was just tackling! Works perfectly.
#2 by Scott Munro on April 1, 2011 - 05:47
This was exactly my problem and your solution worked as advertised – thank you!