ViewModel composition

Aug 11, 2010 at 3:47 PM

I am new to Caliburn and MEF so this might just be my ignorance on how to use both together.

I followed Rob's post on customizing the bootstrapper to use MEF. Now I am trying to figure out how to setup a ViewModel that has another ViewModel in it and get everything to bind together by the framework.

In my ShellViewModel I have a method that takes a ViewModel type, instantiates it and calls ActivateItem.

                    IScreen screen = Activator.CreateInstance(type) as IScreen;
                    ActivateItem(screen);

I have a view model with an inner view model and corresponding views.

LocalView

        <StackPanel>
            <TextBlock Text="Local View" />

            <ContentControl x:Name="InnerViewModel" />
        </StackPanel>

InnerView

        <Border Background="Aqua" Height="50" Width="200">
            <TextBlock x:Name="Title" />
        </Border>

LocalViewModel

    [Export(typeof(LocalViewModel))]
    public class LocalViewModel : Screen
    {
        public LocalViewModel()
        {
//            InnerViewModel = new InnerViewModel();
            
        }
        private InnerViewModel _innerViewModel;

        [Import(typeof(InnerViewModel))]
        public InnerViewModel InnerViewModel 
        {
            get { return _innerViewModel; }
            set { _innerViewModel = value; } 
        }
    }

InnerViewModel

    [Export(typeof(InnerViewModel))]
    public class InnerViewModel : Screen
    {
        public string Title { get; set; }

        public InnerViewModel()
        {
            Title = "It worked!";
        }
    }

When I run it I never get an instance of my inner view model. I know that the framework binding is working fine because if I create the InnerViewModel in the LocalViewModel constructor I can see the InnerView show up inside of the LocalView.

Aug 11, 2010 at 5:58 PM
Edited Aug 11, 2010 at 5:59 PM

If LocalViewModel is created with Activator.CreateInstance (like you are showing in the first snippet), it will not be injected by MEF with appropriate dependencies (InnerViewModel).
You should get the LocalViewModel instance from IoC.GetInstance, which in turn delegates to MEF (as long as you set things up in the bootstrapper).

Aug 11, 2010 at 6:06 PM

Thanks. I kept working at it and figured that part out. However since I don't know my type ahead of time (I am going to download XAPs on demand) I am resolving the type from a string so I can't call IoC.Get<MyType>(). Here is what I am doing right now. Is there a better way?

                Type type = GetType(message.TypeName);
                if (type != null)
                {
                    IScreen screen = GetScreen(type);
                    if (screen != null)
                    {
                        ActivateItem(screen);
                    }
                }


...

        private IScreen GetScreen(Type type)
        {
            MethodInfo methodInfo = typeof (IoC).GetMethod("Get", new Type[] {});

            if (methodInfo != null)
            {
                methodInfo = methodInfo.MakeGenericMethod(type);
                return methodInfo.Invoke(null, null) as IScreen;
            }

            return null;
        }

Aug 11, 2010 at 8:29 PM

You might avoid reflection:

 

public static class IoCEx
{
  public static object Get(Type service)
  {
    return Caliburn.Micro.IoC.GetInstance(service, null);
  }

}
...
var screen = (IScreen)IoCEx.Get(type);

 

Aug 13, 2010 at 2:48 PM
Edited Aug 13, 2010 at 2:51 PM

I did some more reading on MEF and went through the Caliburn code. I understand how the IoC container actually works and realized that I really don't need to resolve the type and then pass it in to the IoC. The method IoC.Get<T>(string key) is what I needed to use. Thanks for the tips.