Confused adding views to screen at runtime

Mar 30, 2011 at 4:54 PM
Edited Mar 31, 2011 at 9:37 AM

I'm using MEF with Calibirn.Micro to try create a plugin/addin architecture WPF App.  Each Addin will be contained in its own project with reference to a common Interfaces library and some common UI components.

I've followed all the getting started guides on delicio.us and the pieces are coming together.  Have had to modify the Configure to support multiple catalogs (maybe there's a nicer way?)

 

var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryCatalog(".", "Addin.*"));
container = new CompositionContainer(catalog);

 

 

My simple ShellView has a grid with two rows, each containing a ContentPresenter with Content bound to a ViewModel object.

I have a list of addin views that MEF injects:

 

[ImportMany(MefTypeNames.ViewContract, AllowRecomposition = true)]
private Lazy<Screen, IViewMetadata>[] _views;

 

My problem is that when I insert the view model, CM cannot find the matching view, the window shows the message "Cannot find view for Addin...."

public object AddinView
{
    get { return _addinView; }
    set
    {
        if (value == _addinView)
            return;

        _addinView = value;
        NotifyOfPropertyChange(() => AddinView);
    }
}
private object _addinView;

#region Implementation of IPartImportsSatisfiedNotification

public void OnImportsSatisfied()
{
    if (_views != null)
    {
        foreach (var view in _views)
        {
            if (view.Metadata.Name == "My Addin")
            {
                this.AddinView = view.Value;
            }
        }
    }
}

#endregion

It would be a really big help to see some samples in the Documentation showing multi-project implementations.

Mar 31, 2011 at 9:37 AM
Edited Mar 31, 2011 at 9:43 AM

Hi,
I am doing very similar to you and can only empathise with the lack of a reference multi-project example.... I think GameLibrary is usually cited, but it is Silverlight and I am working desktop WPF.
However, I have found the HelloScreen example a great help and making a WPF version of it a great learning experience.
I too, have multiple catalogs and an overriden SelectAssemblies. With MEF, one of the things that got me was ImportingConstructor, as if it couldn't be satisfied nothing was initialised. I tend to use individual properties marked with AllowDefault to ensure the Model gets instaniated even if the imports are not available. Also, I'm not Lazy loading, as I couldn't get it to work! So, is OnImportsSatisfied running?
Otherwise,  no views for me was typo errors in the naming of the views and their namespaces, especially when cut & pasting from other - CM's conventions are very unforgiving!
Hope that's a help!!
John

Mar 31, 2011 at 9:54 AM
Edited Mar 31, 2011 at 10:04 AM

OK, after reading the documentation one final time I've made some progress.  As I'm building a Addin style application, my shell that composes shouldn't have any references to the Addins, so I had to override SelectAssemblies() to enable it to find the views in those Addin dlls.  Note that this code is hard coding the dll names but I will change this to do a directory parse to find all files that start with Addin.*.  

 

        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            return new[]
                       {
                           Assembly.GetExecutingAssembly(),
                           Assembly.LoadFrom("Addin.Screen1.dll"),
                           Assembly.LoadFrom("Addin.Screen2.dll")
                       };
        }

 

My ShellViewModel now successfully imports everything and displays the views for the imported ViewModels.

John, I agree that either MEF is pretty unforgiving if a resource is not available, I'm only using it for IoC as I want the Addin features that MEF provides.  Before the application became Addin  based I was using AutoFac which support WPF & Silverlight.  If I was going WPF only I would be using StructureMap still (however I think Jeremy has stopped developing this).

My ShellViewModel now looks like this:

 

namespace PluginDemo.ViewModels
{
    [Export(typeof(IShell))]
    public class ShellViewModel : Conductor<IScreen>, IShell, IPartImportsSatisfiedNotification
    {
        [ImportMany(MefTypeNames.DetailPaneViewContract, AllowRecomposition = true)]
        private Lazy<Screen, IViewMetadata>[] _views;

        [Import(MefTypeNames.BlotterViewContract, AllowRecomposition = true)]
        private Screen _blotter;

        #region Binding Properties

        public object TopArea
        {
            get { return _topArea; }
            set
            {
                if (value == _topArea)
                    return;

                _topArea = value;
                NotifyOfPropertyChange(() => TopArea);
            }
        }
        private object _topArea;

        public object BottomArea
        {
            get { return _bottomArea; }
            set
            {
                if (value == _bottomArea)
                    return;

                _bottomArea = value;
                NotifyOfPropertyChange(() => BottomArea);
            }
        }
        private object _bottomArea;

        #endregion

        #region Implementation of IPartImportsSatisfiedNotification

        public void OnImportsSatisfied()
        {
            if (_blotter != null)
            {
                BottomArea = _blotter;
            }

            if (_views == null) return;

            foreach (var view in _views)
            {
                if (view.Metadata.Name == "Test View")
                {
                    TopArea = view.Value;
                }
            }
        }

        #endregion
    }
}

The ShellView is constructed as follows:

<Window x:Class="PluginDemo.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="ShellView" Height="400" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <ContentControl x:Name="TopArea"></ContentControl>
        
        <GridSplitter x:Name="splitter" Height="6" Grid.Row="1" />

        <ContentControl x:Name="BottomArea" Grid.Row="2"></ContentControl>
    </Grid>
</Window>