Search code examples
c#wpfxamlmvvmmef

ViewModel instatiated twice with MEF


I'm trying to create a simple modular MVVM application with MEF. I have a ViewModel class and a UserControl as the View. I connect the two through a DataTemplate, like this:

<DataTemplate DataType="{x:Type local:MyViewModel}">
    <local:MyView />
</DataTemplate>

In the View, I define the ViewModel as a StaticResource, to make binding simple:

<UserControl.Resources>
    <local:MyViewModel x:Key="ViewModel" />
</UserControl.Resources>

Then I bind like this:

<Grid DataContext="{StaticResource ResourceKey=ViewModel}">
    <TextBlock Text="{Binding Text}" />
</Grid>

This all works as intended without MEF. However, as I am aiming for modularity, I use MEF to discover my ViewModel classes. I have an Export attribute on my ViewModel class:

[Export(typeof(MyViewModel))]
public class MyViewModel
{
    // ...
}

and I use MEF to dynamically load the ViewModel into my shell in App.xaml.cs:

private void Application_Startup(object sender, StartupEventArgs e)
{
    var shell = new MainWindow();
    var catalog = new AssemblyCatalog(this.GetType().Assembly);
    var container = new CompositionContainer(catalog);

    shell.Contents.ViewModel = container.GetExportedValues<MyViewModel>().First();

    shell.Show();
}

Now, at this point, MEF creates an instance of my ViewModel when it loads the vm, and my View creates another instance when it declares the vm as a resource. (This is easily checked by setting a breakpoint in the constructor.)

The question is, how should I pass the instance created by MEF to my resource declaration? Can I declare that specific instance as resource?

DropBox link with full code: https://www.dropbox.com/sh/pbdl029d26sx7gl/AAA6po50dLjbJSoNPBhCyWZ3a?dl=0


Solution

  • Okay, so, what I had to do was this.

    I had two instances because once MEF instantiated the ViewModels when it imported them, and then another time WPF created them, when it created the ViewModel resource. I figured that the solution would be not directly creating the resource, but had no idea how I could manage to do that. Then along came Resource Injection and then DataContextSpy, from this question here: https://stackoverflow.com/a/5402653/5219911

    And here's the direct link to the topic: http://www.codeproject.com/Articles/27432/Artificial-Inheritance-Contexts-in-WPF

    I am now using a resource that is a DataContextSpy, through which I can reach out to the ViewModel instance that is used when creating the DataTemplate.

    In my View's resources, I define and then I just set the root element's DataContext to this resource: DataContext="{Binding Source={StaticResource ResourceKey=ViewModel}, Path=DataContext}"

    Now, unfortunately, I don't get Intellisense support with this alone, as DataContextSpy is sort of a proxy to the real DataContext, so I do have to manually set up the design time DataContext type by using: d:DataContext="{d:DesignInstance Type=viewModel:MyViewModel}"