RadTabControl and View Context

Feb 25, 2011 at 11:44 AM
Edited Feb 25, 2011 at 3:38 PM

Edit: Ignore this, no bug, just a bunch of gotchyas when dealing with the RadTabControl, there is still a comment (but no breaking issue) dealing with using both View.Context and View.Model at the bottom.

Using RC1 build in Silverlight 4 I have a Telerik RadTabControl where I want all the Views in every tab to share the parent view model but have different view contexts. So I have it setup like this:

 

<Grid.Resources>
	<Behaviors:DataContextSpy x:Key="DCSpy" />

	<DataTemplate x:Key="HeaderTemplate">
		<TextBlock Text="{Binding Path=DisplayName}" />
	</DataTemplate>

	<DataTemplate x:Key="ContentTemplate">

		<ContentControl Micro:View.Model="{Binding Path=DataContext, Source={StaticResource DCSpy}}"
		                Micro:View.Context="{Binding Path=ViewContext, Mode=TwoWay}"
		                HorizontalContentAlignment="Stretch"
		                VerticalContentAlignment="Stretch" />
	</DataTemplate>
</Grid.Resources>

<Controls:RadTabControl TabOrientation="Vertical"
			                        TabStripPlacement="Left"
			                        ItemsSource="{Binding Path=EditViews}"
			                        ItemTemplate="{StaticResource HeaderTemplate}"
			                        ContentTemplate="{StaticResource ContentTemplate}"/>

 

 

 

However I kept getting an exception in the Caliburn View.cs class becasue it was trying to set the Content property to null, which was throwing an exception. I was able to fix it by changing the following in View.cs from this:

private static void SetContentProperty(object targetLocation, object view)
{
    var fe = view as FrameworkElement;
    if (fe != null && fe.Parent != null)
        SetContentPropertyCore(fe.Parent, null);
       
    SetContentPropertyCore(targetLocation, view);
}


to this:

private static void SetContentProperty(object targetLocation, object view)
{
    var fe = view as FrameworkElement;
    if (fe != null && fe.Parent != null)
        SetContentPropertyCore(fe.Parent, view);
       
    SetContentPropertyCore(targetLocation, view);
}

Did I find a bug or am I doing something wrong?

Edit: Even with the above fix I now have a secondary issue, when I navigate to a different and then go back to the first tab the content of the previously properly populated tab is empty.

Edit 2: I think I know what's causing the secondary issue. The way the tab control works is on selection change it removes the tab content from the visual tree and creates a new instance of content control when tabbed back in, except the view instance resolved by view.cs is the previous view that's now detached from the visual tree that it tries to attach to the new content control instance. The second issue is not necessarily a Caliburn issue, I'll look to maybe if it's possible to override this behavior in the RadTabControl.

Edit 3: Ok, it looks like this is the bug causing issue 2:
http://www.telerik.com/community/forums/silverlight/tabcontrol/tabcontrol-with-contenttemplate-bindning-to-custom-controls.aspx#1273822

Any suggestions on a Caliburn friendly way to apply the above fix?

Edit 4: It looks like using the following extended version of the RadTabControl fixed issue # 2 for me:

http://www.friendlyninja.com/2010/12/01/telerik-radtabcontrol-and-dynamic-tab-generationdisposal/

Edit 5: Spoke too soon, works fine until I try to remove and re-add the same view context... Back to the drawing board. 

Edit 6: Ok I restored the original View.cs and in combination with the new RadTabControlEx everythign seems kosher so far, so my only renaming comment is below.


On another note I don't think I really like the syntax of setting View context and View Model attached properties because the view model attached property sets the content to an incorrect view before the context sets the content to a correct view. Wouldn't it be better to set the DataContext of the ContentControl explictly and change the context setter to something like this?

private static void OnContextChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs e)
{
    if (e.OldValue == e.NewValue)
        return;
    
    var model = GetModel(targetLocation);
    if (model == null)
    {
        var frameworkElement = targetLocation as FrameworkElement;
        if (frameworkElement != null)
        {
            model = frameworkElement.DataContext;
        }
    }

    if (model == null)
    {
        return;
    }
    var view = ViewLocator.LocateForModel(model, targetLocation, e.NewValue);      

    SetContentProperty(targetLocation, view);
    ViewModelBinder.Bind(model, view, e.NewValue);
}