Trying to understand Navigation

Sep 3, 2010 at 6:32 PM

Hello,

I am really new to caliburn micro (no prev caliburn experience) and fairly new to MVVM. I am trying to understand how the navigation works in Micro.

I am using MEF as the bootstrapper like in the example.

My shellview has one contentcontrol. The shellviewmodel is a Conductor<IScreen>, IShell.ViewModel.

 

    <Grid>
        <StackPanel>
            <TextBlock Text="{Binding Title}" FontSize="18" Margin="5 0 5 5" />
            <ContentControl x:Name="MyContent" cal:View.Model="{Binding Path=MyContentViewModel}" />
        </StackPanel>
    </Grid>

    [Export(typeof(IShellViewModel))]
    [Export(typeof(ShellViewModel))]
    public class ShellViewModel : Conductor<IScreen>, IShellViewModel
    {
        [Import(typeof(IRegionViewModel))]
        public IRegionViewModel MyContentViewModel { get; set; }

 

The content once set with the right view model has a listview. When the user clicks a listviewitem I want to navigate them to another viewmodel/view.

    [Export(typeof(IRegionViewModel))]
    public class RegionViewModel : Conductor<IScreen>, IRegionViewModel, IScreen
    {
        [Import(typeof(ShellViewModel))]
        private ShellViewModel ShellVM { get; set; }

In my action method for the selectionchanged, I create the new viewmodel, set properties on it and then call ActivateItem(freshViewModel). But nothing happens after this.

       <ListView x:Name="SubRegions"
                  cal:Message.Attach="[Event SelectionChanged] = [Action ShowSubRegion(SubRegions.SelectedValue)]">

        public void ShowSubRegion(Region selValue)
        {
            if (selValue == null) return;
            var regionVM = new RegionViewModel();
            regionVM.RegionToShow = selValue;
            var screen = regionVM as IScreen;
            //ActivateItem(regionVM);
            ShellVM.ActivateItem(screen);
        }
In this case, I want to bring up the sameviewmodel with different region.

Are we expected to override ActivateItem?

I created an import for the shellviewmodel as a property and tried shellViewModel.ActivateItem(freshViewModel) with the same result.

Is passing the shellviewmodel as a dependency the suggested pattern?

I am thinking, I have not understood the concepts behind navigation as it pertains to Micro.

Could someone help me understand how I should be handling navigation and what I am doing wrong?

Thanks.

 

 

 

Sep 3, 2010 at 8:54 PM

I am assuming this is WPF since I dont' recall a ListView in Silverlight. Don't quite follow exactly what you are trying to do but if you call your ContentControl "ActiveItem" then Caliburn will automatically load the correct view when you call the ActivateItem method. So I would remove the attached property cal:View.Model and then in your constructor call ActivateItem for your RegionViewModel property. You may need to have MEF inject your original  viewmodel by using the [ImportingConstructor] attribute on it like this:

        [ImportingConstructor]
        public ShellViewModel(IRegionViewModel regionViewModel)
        {
            MyContentViewModel = regionViewModel;
            ActivateItem((IScreen)MyContentViewModel);
        }

 Now when you call ShellVM.ActivateItem(screen) the right view should get loaded in your content control.

Sep 3, 2010 at 10:55 PM

ralmlopez,

Thanks for the response. I followed your steps and view started updating.

Will activateitem automatically set the Parent property of the screen viewmodel to itself (conductor)?

Is the recommended approach to use the "Parent" property to activate a viewmodel

or

pass the shellviewmodel as a constructor argument to all the following view models?

Thanks once again.

Appreciate it.

 

 

Sep 3, 2010 at 11:26 PM

It will set the parent property as long as your view model inherits from Screen or you implement the IChild<IConductor> interface (look at last method in ConductorBase EnsureItem). As far as the best practice I am not sure, I am learning same as you. Maybe someone else can give some guidance on best practice.

Sep 4, 2010 at 9:21 PM

I'm glad you solved the issue, yet I think it may be useful to clarify (hopefully) what was wrong in the first version.

The cal:View.Model property is exactly the same used by the infrastructure to bind view to VM using conventions; so naming the ContentControl "ActiveItem" (thus making use of convention, as ralmlopez correctly pointed out) or using an explicit binding cal:View.Model="{Binding ActiveItem}" is exactly the same. 
Calling ActivateItem in ShellVM code has the effect of populating ActiveItem property (other than enforcing lifecycle methods on the child VM).

The actual problem in the binding was the property ShellViewModel.MyContentViewModel because the VM (exposed by that) never got touched by the ShowSubRegion method; each call will have created and activated a new RegionViewModel instance that won't ever get the opportunity to display itself in the root view because ShellViewModel.MyContentViewModel was never updated.

About using Parent property vs using an injected instance of the Shell... well, it depends (as usual). It's only a modeling concern: think of the application as a tree of nested conductors and VMs (screens):
- if you want a VM (which could eventually be used in different depth down the application tree) to operate on its immediate ancestor, then you should use Parent reference;
- if otherwise you always want to operate on the root conductor (shell) regardless of the current depth then the injected reference is the way to go.

Hope it helps.

Sep 4, 2010 at 11:19 PM

Marco,

Thanks for the response. I want to see if I understood your explanation of what was wrong in original post.

The actual problem in the binding was the property ShellViewModel.MyContentViewModel because the VM (exposed by that) never got touched by the ShowSubRegion method; each call will have created and activated a new RegionViewModel instance that won't ever get the opportunity to display itself in the root view because ShellViewModel.MyContentViewModel was never updated.

So If I had overriden the ActivateItem in the shellviewmodel like

       private IScreen _myContentViewModel;
       public IScreen MyContentViewModel
       {
           get { return _myContentViewModel; }
           set 
            { 
                  _myContentViewModel = value;
                  NotifyPropertyChanged(() => MyContentViewModel);
             }
        }

        public override void ActivateItem(IScreen item)
        {
           MyContentViewModel = item;
            base.ActivateItem(item);
        }

would that have worked?

Sep 5, 2010 at 9:00 AM

I guess that it would have, but you would eventually have got a small issue with activation in a particular scenario: since is not guaranteed that an activation is always successful (for example, because the previously active screen refuses to shut down) you should only change the MyContentViewModel when activation succeedes:

 //screenVM
protected override void OnActivationProcessed(T item, bool success) {
	if (success)
		 MyContentViewModel = item;
	base.OnActivationProcessed(item, success);
}

This was just to illustrate the concept, though: since Conductor is built with the exact purpose to track a single current VM, you can more easily use ActiveItem property directly in most scenarios.

Sep 5, 2010 at 1:53 PM

Marco,

Thanks for the clarification. I really appreciate it.

I have switched my code to use the ActiveItem but it is good to understand what I was doing wrong in my earlier implementation.