0

I have built a modular program using

http://www.codeproject.com/Articles/258681/Windows-Forms-Modular-App-using-MEF as a base and I have several of my modules working.

It is a MDI Windows Forms application and I need to call back to the host module for some stuff.

a) Location information for the MDI host window

b) Write to the status bar in the host window.

I have managed to get the application to compile but when I call the host functions it always gives me a null exception

I did look at Using MEF with C#, how do I call methods on the host, from the plugin?

which is where I got my line

public Exec.Core.Interfaces.IHost Host;

But host is always null so I get exceptions trying to access the members of MDIForm which is the host.

Even if I do public Exec.Core.Interfaces.IHost Host {get;set;}

This is the Host.

NameSpace Exec
{
  [Export(typeof(Exec.Core.Interfaces.IHost))]
   //  MDIForm is the host.    
  public partial class MDIForm : Form, Exec.Core.Interfaces.IHost
  {
     ///other stuff not related to the problem


      // defined in public interface IHost
    public Point myLocation()
    {
      return this.Location;  // need the window location

    }
      // defined in public interface IHost
    public IHost GetHost()
    {  // is this what GetHost Should Return? Not sure
      return this;
    }
      // defined in public interface IHost
    public void SendMessage(string message)
    {
      SetStatusBar(message); // print a message to MDIForm status bar
    }
  }
}

Then is the IHosts.cs

namespace Exec.Core.Interfaces
{
    public interface IHost
    {
        IHost GetHost();
        void SendMessage(string message);
        Point myLocation();
       // MDIForm GetThis( );  /* this gives error. Can't resolve MDIForm
                                  I don't know why and can't resolve.*/
    }
}

This is one of the modules where I am trying to get stuff from the host

namespace Exec.Modules.Tasks
{
   [Export]
   public partial class frmTasks : Form
   {
       [Import(typeof (Exec.Core.Interfaces.IHost))] 
       public Exec.Core.Interfaces.IHost Host;
           // unfortunately Host == NULL at this point

       private void SendMessage (string message)
       {
           try
           {
              Host.SendMessage(message);  <Throws System.NullReferenceException
           }
           catch (Exception ex)
           {
              MessageBox.Show(ex.ToString());
           }
       }
       private IHost WhichHost()
       {
           try
           {     /// not really sure what will be returned here
               return GetHost();<Throws System.NullReferenceException
           }
           catch (Exception ex)
           {
               MessageBox.Show(ex.ToString());

            }
       }
       private Point Location()
       {
          try
          {
             return mylocation(); <Throws  System.NullReferenceException
          }
          catch (Exception ex)
          {
              MessageBox.Show(ex.ToString());
          }
       }
   }
}

And finally this is how I put all of the objects together in ModuleHandler.cs This is pretty much taken from the codeproject above with some seperation of some method calls into 2 pieces so I could see why it was dying.

 namespace Exec.Core   
 {
     [Export(typeof(IModuleHandler))]
     public class ModuleHandler : IDisposable, IModuleHandler
     {

        [ImportMany(typeof(IModule), AllowRecomposition = true)]
         // The ModuleList will be filled with the imported modules
        public List<Lazy<IModule, IModuleAttribute>> ModuleList
        { get; set; }

        [ImportMany(typeof(IMenu), AllowRecomposition = true)]
        // The MenuList will be filled with the imported Menus
        public List<Lazy<IMenu, IModuleAttribute>> MenuList { get; set; }

        [Import(typeof(IHost))]
        // The imported host form
        public IHost Host { get; set; }


       AggregateCatalog catalog = new AggregateCatalog();
       public void InitializeModules()
       {
          // Create a new instance of ModuleList
          ModuleList = new List<Lazy<IModule, IModuleAttribute>>();
          // Create a new instance of MenuList
          MenuList = new List<Lazy<IMenu, IModuleAttribute>>();

          // Foreach path in the main app App.Config      
          foreach (var s in ConfigurationManager.AppSettings.AllKeys)
          {
            if (s.StartsWith("Path"))
            {
             // Create a new DirectoryCatalog with the path loaded from the App.Config
                DirectoryCatalog cataloglist = new DirectoryCatalog(ConfigurationManager.AppSettings[s], "jobexe*.dll");
               catalog.Catalogs.Add(cataloglist);
             }
           }
            // Create a new catalog from the main app, to get the Host
           catalog.Catalogs.Add( new AssemblyCatalog(System.Reflection.Assembly.GetCallingAssembly()));
                    // Create a new catalog from the ModularWinApp.Core
            DirectoryCatalog catalogExecAssembly = new DirectoryCatalog( System.IO.Path.GetDirectoryName(
              System.Reflection.Assembly.GetExecutingAssembly().Location ), "exe*.dll");

            catalog.Catalogs.Add(catalogExecAssembly);
            // Create the CompositionContainer
            CompositionContainer cc = new CompositionContainer(catalog);

            try
            {
                cc.ComposeParts(this);
            }
            catch (ReflectionTypeLoadException e)
            { MessageBox.Show(e.ToString()); }

            catch (ChangeRejectedException e)
            { MessageBox.Show(e.ToString()); }     
        }            
    }
}

So again, the modules work independently but are unable to call back to the host. Wondering what I am doing wrong.

Thanks in advance for any help

One final thing that may have something to do with the issue.

Here is the code that starts the program

 public static ModuleHandler _modHandler = new ModuleHandler();

static void Main()
{
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);

  //Initialize the modules. Now the modules will be loaded.
   _modHandler.InitializeModules();
    // this goes straight to class MDIForm()  constructor
  Application.Run(_modHandler.Host as Form);

}

Colin

Community
  • 1
  • 1
BrownPony
  • 575
  • 4
  • 12

2 Answers2

0

I have an answer, I just don't think its the right one. On my question I edited it and added the main program but i did not add its class.

It looks like

namespace Exec
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        /// 
        //Create a new instance of ModuleHandler. Only one must exist.
        public static ModuleHandler _modHandler = new ModuleHandler();

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            //Initialize the modules. Now the modules will be loaded.
          _modHandler.InitializeModules();


          Application.Run(_modHandler.Host as Form);


        }
    }
}

I was debugging and I found that in _modHandler.InitializeModules();

IHost Host was set, and its part of

public static ModuleHandler _modHandler = new ModuleHandler();

and everything here is static but not accessable. So I changed the class signature to public to make it global (dirty word I know)

public static class Program

and then in

namespace Exec.Modules.Tasks

in the Load_Form event I added a line to initialize Host.

public partial class frmTasks : Form
{
       [Import(typeof (Exec.Core.Interfaces.IHost))] 
       public Exec.Core.Interfaces.IHost Host;

      private void Load_Form(object sender, EventArgs e)
      {
         Host = Program._modHandler.Host.GetHost; << added this to initialize host

           other stuff....
      }
      other stuff that now works
}

I don't think that this is how its supposed to work. I think that I should be able to populate this through the interfaces and modules...

Comments?

BrownPony
  • 575
  • 4
  • 12
0

You instantiate your ModuleHandler manually and then call InitializeModules, where a catalog is created and passed to a new composition container. Then, this container is used to satisfy all the imports of that particular ModuleHandler instance through the line:

cc.ComposeParts(this);

This tells MEF to look for Import attributes and populate the decorated properties with instances of classes decorated with the corresponding Export attributes.

What you are missing is the analogous call to populate your frmTasks objects. Thus, the following Import is not satisfied and the property is null:

[Import(typeof (Exec.Core.Interfaces.IHost))] 
public Exec.Core.Interfaces.IHost Host;

You have several options among which I'd look into the following two:

  • Modify the IModule interface so that you can explicitly pass an IHost to the modules. Then, in the InitializeModules, after the call to ComposeParts, iterate over the composed modules passing them the host instance. This accounts for setting the Host property through the IModule interface. You could also stick to the MEF imported property by putting it in the IModule interface and calling ComposeParts for each module instance.

  • Expose the container through a ServiceLocator and get the IModuleHandler instance from the modules to access the Host property.

jnovo
  • 5,659
  • 2
  • 38
  • 56
  • Thanks your comment made me go back and look the IModule Interface again and there was already was already a call to `IHost Host { get; set; }` It was being used to set the MdiParent on the form with the value of IHost. I added `Host = (IHost) this.MdiParent;` in the Load_Form event and all was good. Thank you. – BrownPony Jan 12 '16 at 19:44