0

How do I bind data from host to a plugin with MEF?

So the thing is:

  • I work with MVVM so I have my Models, ViewModels and Views.
  • I want to use MEF to be able to expand my application.
  • I want to store all the data in the MainViewModel so every plugin can work with the actual data.
  • The plugin is a UserControl wich will be displayed as a ContentControl in the MainViewModel.

What I have so far:

  • MainViewModel
  • Models
  • Databinding from MainViewModel to View.
  • Import plugins from folder X

What I need: - the plugins need to bind the data from the MainViewModel to the plugin UI. - changing the property in the plugin UI must update the data in the MainViewModel and update the UI from all other plugins.

The PluginInterfaces:

public interface IPlugin
{

}
   public interface IPluginData
{
   string Name { get; }
}

The MainViewModel: (part of it)

private MyModel myfirstmodel; 
private DirectoryCatalog catalog;
private CompositionContainer container;

[ImportMany] 
IEnumerable<Lazy<IPlugin, IPluginData>> Plugins;

public MainWindowViewModel()
{
    string pluginPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    pluginPath = Path.Combine(pluginPath, "plugins");
    if (!Directory.Exists(pluginPath))
    Directory.CreateDirectory(pluginPath);
    catalog = new DirectoryCatalog(pluginPath, "*.dll");
    container = new CompositionContainer(catalog);

    try
    {
        this.container.ComposeParts(this);
    }
    catch (CompositionException compositionException)
    {
        Console.WriteLine(compositionException.ToString());
    }
}

The Model

public class MyModel
{
    private string message;
    private int number;
    private DateTime date;

    public string Message { get { return message; } set { message = value; } }
    public int Number { get { return number; } set { number = value; } }
    public DateTime Date { get { return date; } set { date = value; } }
}

The Plugin

[Export(typeof(IPlugin))]
[ExportMetadata("Name", "MyFirstPlugin")]
public partial class MyFirstPlugin : UserControl, IPlugin
{

    public MyFirstPlugin()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        //Change the message in MainWindowViewModel and the date when it gets changed.
    }
}

I tried using INotifyPropertyChanged but did not came that far..

Does anybody got a really good tutorial for that or can show me how to do this? I would appreciate a "how to" and not just a "just use INotifyPropertyChanged".

Is this even possible?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
bene_rawr
  • 119
  • 1
  • 10

2 Answers2

0

For exchange data with ModelView you will need an interface representing the host. You will also need a container or method to insert a visual control representing the plugin.

 interface IPluginHost 
 {
      MyModelView ModelView {get;}
      MyMainWindow View {get;}
 }

 interface IPlugin
 {
    void Register(IPluginHost host);
    void Unregister();
 }

After the composition you register plugins with the host, which will gives them an access to the model view:

this.container.ComposeParts(this);
foreach(var plugin in Plugins)
{
     plugin.Value.Register(this);
}

What I need: - the plugins need to bind the data from the MainViewModel to the plugin UI.

Plugin has access to MainViewModel in it's Register method were it can perform all the necessary bindings. Here is one of many ways it could be done:

 public partial class MyFirstPlugin : UserControl, IPlugin
 {
      ...

     void Register(IPluginHost host)
     {
          _host = host;

           // Attach main model view as data context
           this.DataContext = host.ModelView; 

           // Add control to the host's container
           var mainWindow = host.View;

           mainWindow.AddPluginControl((UserControl)this);
     }

     void Unregister()
     {
          if (_host == null)
          { 
              return;
          }

          this.DataContext = null;
          _host.View.RemovePluginControl(this);
          _host = null;
     }

     IPluginHost _host;

 }

AddPluginControl() ,RemovePluginControl() are public methods to insert a visual element of the plugin into container.

alexm
  • 6,854
  • 20
  • 24
  • Well.. i dont get the part with the registration.. Where does the method 'Register' come from and what does it? Would be nice to get a clue how to solve this. :c – bene_rawr Oct 02 '15 at 15:40
  • Register is a method that I've added to IPlugin interface declaration.I have added an example to the answer. – alexm Oct 02 '15 at 17:27
  • Okay. I get this with the Plugin registration now. But where do I use the IPluginHost interface? I have one ModelView which is used in IPluginHost and compose all the plugins. But when I add IPluginHost to ModelView I have to set IPluginHost.MyModelView in MyModelView.. and that confuses me.. – bene_rawr Oct 03 '15 at 06:46
0

Personally I think you're going about this the wrong way, MEF was specifically designed to do this type of plumbing so that you don't have to.

In a proper MVVM application the views usually get created by way of data templates, and even if you don't do this there is usually other global data you need to import like brushes and behaviours etc. And if you do use templates (which you really should be doing) then you don't need to explicitly export your controls; simply referencing them by the templates will result in them getting imported as well.

You can achieve this in practice with another pass of the importer. Give each plugin a custom ResourceDictionary and put all the global data/templates/UI resources etc in there (so it's effectively the plugin's version of App.xaml). The trick is to then give those ResourceDictionaries their own code-behind file and decorate those classes with Export. Then all your main application has to do is Import all the classes of type ResourceDictionary (or preferably a derived class or interface that returns the dictionary) and add each one to Application.Current.Resources.MergedDictionaries. From that point on the development of your plugins is pretty much the same as if they were in a DLL project that you were statically linking the usual way.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • Can you give me short example on how to do this? Never worked with ResouceDirectorys before.. :c – bene_rawr Oct 05 '15 at 18:06
  • I found the SO post from 6 years ago where I originally learned this myself, it's [the upvoted answer to this question](http://stackoverflow.com/questions/842571/using-mef-to-import-a-wpf-datatemplate). You just have to remember to do the `InitializeComponent();` in the constructor, otherwise the dictionary will appear empty when you import it. – Mark Feldman Oct 06 '15 at 21:46
  • Thanks for that. I'll try it. :) – bene_rawr Oct 09 '15 at 14:38
  • I would like to add a UserControl to my application. This UserControl will be added in a overall "view" and bind data from the View. And when the data in the host changes, the usercontrol hast to change too. How do I create that? – bene_rawr Nov 12 '15 at 17:44
  • @bene_rawr you might have to ask this as a second question with more specific details about your exact case because there are a number of different ways in which it's done. Generally speaking you either use a `ContentControl` with a `DataTemplate`, or you use a `Style` with a data trigger. – Mark Feldman Nov 16 '15 at 19:52
  • Okay, did that over here. http://stackoverflow.com/questions/33743792/how-to-bind-data-from-view-to-usercontrol-with-soapbox – bene_rawr Nov 16 '15 at 20:13
  • could you have a look at my other question? :x – bene_rawr Nov 24 '15 at 19:20
  • @bene_rawr I've added an answer to that page. – Mark Feldman Nov 26 '15 at 04:42