View not connecting to ViewModel using MEF

Topics: Prism v4 - Silverlight 4
Dec 11, 2010 at 4:27 PM

I have a view called HomeView that has a nested view called HelloView. HomeView has no view model, but the nested HelloView does. I am trying to connect HelloViewModel to HelloView using an import in HelloView. However this is not working - the view model reference is null. Can someone please suggest how to do this? My code is shown below:

***** HomeView.xaml *****
<UserControl ...>
    <Grid x:Name="LayoutRoot">
        <StackPanel>
            <TextBlock Text="Home" />
            <view:HelloView x:Name="helloView"/>
        </StackPanel>
    </Grid>
</UserControl>


***** HomeView.xaml.cs *****
[ViewExport(RegionName = "MainRegion")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class HomeView : UserControl
{
    public HomeView()
    {
        InitializeComponent();
    }
}


***** HelloView.xaml *****
<UserControl ...>
    <StackPanel x:Name="LayoutRoot" Margin="10">
        <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
            <TextBlock Text="Name" VerticalAlignment="Center" />
            <TextBox Width="100" VerticalAlignment="Center" Margin="10 0 0 0"
                     Text="{Binding Path=Name, Mode=TwoWay}" />
            <Button Content="Submit" VerticalAlignment="Center" Margin="10 0 0 0"
                    Command="{Binding SubmitCommand}"/>
        </StackPanel>
        <TextBlock Text="{Binding Message}" Margin="0 10 0 0" Foreground="Red" />
    </StackPanel>
</UserControl>


***** HelloView.xaml.cs *****
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class HelloView : UserControl
{
    public HelloView()
    {
        InitializeComponent();
    }

    [Import]
    public IHelloViewModel ViewModel
    {
        set { this.DataContext = value; }
    }
}


***** IHelloViewModel.cs *****
public interface IHelloViewModel
{
}

***** HelloViewModel.cs *****
[Export(typeof(IHelloViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HelloViewModel : NotificationObject, IHelloViewModel
{
    public HelloViewModel()
    {
        this.SubmitCommand = new DelegateCommand<object>(this.OnSubmit);
    }

    private void OnSubmit(object obj)
    {
        Message = "Hello " + Name;
    }

    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (value != _name)
            {
                _name = value;
                this.RaisePropertyChanged("Name");
            }
        }
    }

    private string _message;
    public string Message
    {
        get { return _message; }
        set
        {
            if (value != _message)
            {
                _message = value;
                this.RaisePropertyChanged("Message");
            }
        }
    }

    public ICommand SubmitCommand { get; private set; }
}

Interesting thing is that if I export the nested view (HelloView) into the MainRegion instead of the parent view (HomeView), then HelloView gets properly connected to HelloViewModel and everything works perfectly. So I have a hunch that by nesting HelloView, its construction does not go through MEF.

Thanks.

Naresh

Dec 13, 2010 at 7:58 PM

Hi Naresh,

Based on my understanding, you are trying to instantiate your HelloView from the HomeView.xaml. It is likely MEF is not satisfying the import of your viewmodel, since the view is instantiated in the XAML.

You might try the following approaches to solve this issue:

  • You could use the ServiceLocator to get the instance of your viewmodel. For example:

public HelloView()
{
     this.InitializeComponent();

this._helloViewModel = ServiceLocator.Current.GetInstance<IHelloViewModel>();

}

  • Another approach might be to add a region to your HomeView.xaml using Scoped Regions and register your HelloView in it. That way, your view will be composed when it gets added to that region. You can read more about in Chapter 7: Composing the User Interface.

 

  • On the other hand, you could try using the SatisfyImports method from CompositionInitializer class to satisfy your imports when the HelloView is added. You can read about it here.

 

I hope you find this information useful.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

Dec 14, 2010 at 5:44 PM

Thanks Miguel. I like your approach #2. However, I was able to achieve the desired result using a simple region, did not have to create a scoped region. Any reason you suggested a scoped region?

Here's my modified code:

***** HomeView.xaml *****
<UserControl ...>
    <Grid x:Name="LayoutRoot">
        <StackPanel>
            <TextBlock Text="Home" />
            <ContentControl
                prism:RegionManager.RegionName="HomeContentRegion" />
        </StackPanel>
    </Grid>
</UserControl>

***** HelloView.xaml.cs *****
[ViewExport(RegionName = "HomeContentRegion")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class HelloView : UserControl
{
    public HelloView()
    {
        InitializeComponent();
    }

    [Import]
    public IHelloViewModel ViewModel
    {
        set { this.DataContext = value; }
    }
}

Also, I think I have seen CreationPolicy.Shared on views in some examples. Any reason why a view would be marked as Shared?

Thanks.

Naresh

Dec 14, 2010 at 7:27 PM

Hi Naresh,

Thanks for sharing this with the rest of the community, since it might help other users in a similar scenario as you mentioned.

The reason I suggested you to use Scoped Region is that, when you instantiate a view which defines a region, the view will inherit the parent container´s RegionManager. Since every time in your application creates more than one instance of that view, each instance would attempt to register its region with the parent RegionManager. RegionManager allows only uniquely named regions; therefore, the second registration would produce an error. To avoid these kinds of errors, you could use Scoped Regions, so that each time a new instance of your view is created, a new RegionManager is attached to it.

Regarding your last question, depending on your desired scenario you could benefit from having a view export marked as Shared. For example, if you wish to keep certain state (such as the checked property of a checkbox) in your view even when it's removed from a region, retrieving the same instance of the view each time you compose it could be useful.

You can read more about this here.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

 

Dec 14, 2010 at 11:08 PM

Thanks Miguel. That clarifies both my questions.

Naresh