View.Model attached property sets to improper view before View.Context

Feb 25, 2011 at 7:32 PM
Edited Feb 25, 2011 at 7:33 PM

Environment: Caliburn Micro RC1 with Silverlight 4


I've created a new thread since the previous thread got hard to read as it got long in the tooth while I logged my progress of various workarounds:

http://caliburnmicro.codeplex.com/discussions/247555

I'm trying to bind a collection of View Contexts to a RadTabControl (every tab points the parent viewmodel but has it's own view). Most of the issues I encountered were resolved by using an extended version of the RadTabControl found here:

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

There's one final issue I'm encountering which I'm currently dealing with with a modification to Caliburn's View.cs. My last issue is that View.Model sets the content control in the tab DataTemplate to an invalid value (to the parent view which causes the entire view not to render):

My work-around was to change the ContentTemplate (which defines a tab's content) from this:

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

to this:

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

And to change View.cs from this:

private static void OnContextChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs e)
{
    if (e.OldValue == e.NewValue)
        return;

    var model = GetModel(targetLocation);
    if (model == null)
        return;

    var view = ViewLocator.LocateForModel(model, targetLocation, e.NewValue);

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

to 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);
}

What's the best way to deal with this issue without modifying the Caliburn source?      

Feb 25, 2011 at 7:56 PM

Alright, I found a work-around that requires no modification to View.cs. I've encountered this issue before, apparantly setting a binding with a source messes with property resolution order. I've had the same issue with ComboBox ItemsSource/Selected item when bound with a source specified on the binding. So my work around was to expose the parent view model (the viewmodel hosting the item context collection) as a property of the collection. I was then able to write out the template like this, which worked without issue:

<ContentControl                                                                                                                        
	Micro:View.Context="{Binding Path=ViewContext}"                                                                        
	Micro:View.Model="{Binding Path=Parent}"	
	HorizontalContentAlignment="Stretch"
	VerticalContentAlignment="Stretch" />