MEF + jak wywołać formę z DLL'ki

0

Witam, korzystam z Managed Extensibility Framework - w skórcie MEF, do tworzenia mechanizmu pluginów.

Tak więc mam okno główne - rodzica. Rodzic jest kontenerem dla okien MDI, tak więc IsMdiContainer = True.

Następnie, w nowym projekcie stworzyłem sobie formę, położyłem button, podpiąłem MessageBox i skompilowałem jako DLL.

Wszystko fajnie, jednak nie wiem jak utworzyć interfejs, lub w ogóle sprawić, żeby po kliknięciu na button (w oknie głównym) została otworzona forma, która obecne znajduje się w DLL'ce :/

Btw. jestem początkujący w MEF

Dodam jeszcze, że w prezencie, przy próbie kompilacji formy w DLL dostaję :
Error 1 'MEF_Form.Form1' does not implement interface member 'MEF_Interfejs.IMessageSender.Send(string)' D:\Projekty\MEF\MEF_Form\MEF_Form\Form1.cs 15 26 MEF_Form

Źródła

Program główny

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using MEF_Interfejs;


namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            new MefGospodarz().Run();
        }
    }

    class MefGospodarz
    {
        [Import]
        public IMessageSender MessageSender { get; set; }

        private void Compose()
        {
            AggregateCatalog katalog = new AggregateCatalog();
            katalog.Catalogs.Add(new DirectoryCatalog(@".\wtyczki"));

            if (katalog.Parts.Count() > 0)
            {
                //wyswietlenie wtyczek
                var kolekcja = katalog.Parts;
                string s = "Znalezione wtyczki : \n";
                foreach (var element in kolekcja)
                    s += element.ToString() + "\n";
                MessageBox.Show(s);

                CompositionContainer pojemnik = new CompositionContainer(katalog);
                pojemnik.ComposeParts(this);
            }
        }

        public void Run()
        {
            Compose();
         //   if (MessageSender != null)
          //  {
          //      if (!MessageSender.Send("Treść wiadomości"))
              //      MessageBox.Show("Nie udało się dostarczyć wiadomości");
          //  }
          //  else MessageBox.Show("Brak wtyczki pozwalającej na dostarczenie wiadomości");
        }
      
    } 
}

Interfejs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MEF_Interfejs
{
    public interface IMessageSender
    {
       bool Send(string message);
    // ??    Form pp = new Form();
    }
}

Forma w DLL

using System.Windows.Forms;
using System.ComponentModel.Composition;
using MEF_Interfejs;

namespace MEF_Form
{
    [Export(typeof(IMessageSender))]
    public partial class Form1 : Form, IMessageSender
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("DUPA BLADA");
        }
    }
}
0

Opis błędu mówi o tym, że MEF_Form.Form1 nie implementuje metody MEF_Interfejs.IMessageSender.Send(string). No i (niespodzianka) faktycznie tak jest. Gdzie masz metodę Send w klasie MEF_Form.Form1?

0

Ja rozumiem jaki jest błąd, ale nie wiem jak sprawić, co napisać, żeby button otworzył mi tylko Formę znajdującą się w DLL'ce. W dalszej kolejności, otwarta forma ma odczytać dane do bazy, połączyć się z nią i wyświetlić dane - na chwilę obecną nic więcej. Mój problem to właśnie to, że nie wiem co wpisać do interfejsu, żeby móc otworzyć formę w postacie takiej jaka jest.

0

w MEF_Form.Form1 musisz zaimplementować interfejs MEF_Interfejs.IMessageSender, czyli metodę bool Send(string message); W twoim kodzie tego nie ma.

namespace MEF_Form
{
    [Export(typeof(IMessageSender))]
    public partial class Form1 : Form, IMessageSender
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("DUPA BLADA");
        }
public bool Send(string message)
{
  //coś tu robię z message
}
    }
}

Gdzie MefGospodarz() ma ustawiane MessageSender?

A poza tym to ja nie ogarniam twojej koncepcji i nie chce mi się myśleć co autor miał na myśli.
Może dokładniej opisz cel, bo ty chyba architektura rozwiązania kuleje.

0

Może faktycznie, zbyt chaotycznie opisałem problem :/
Tak więc...

Chcę zrobić aplikację główną i do niej wtyczki (pluginy).
Aplikacja główna to kontener MDI, wtyczki (czyli pliki DLL) mają zawierać w sobie formy, na których położone są np. buttony, datagrid etc.

Co chciałbym osiągnąć :

  1. odpalam program główny
  2. zostaje podpięta pod menustrip wtyczka np. Uzytkownicy
  3. klikam pozycję z menu -> Użytkownicy -> zostaje załadowana forma z pliku DLL (wtyczki) -> forma zostaje pokazana jako okno MDI, którego rodzicem jest program główny.

Moim problemem jest to, iż nie wiem co mam napisać, żeby po kliknięciu na wybraną opcję menu, pokazać (wywołać) formę z DLL. Dodatkowo, forma ta ma być dzieckiem (MDI) dla programu głównego.

0

Przede wszystkim musisz zmienić interfejs, który implementować bęą pluginy. W interfejsie muszą się znaleźć metody, które będą wykonywały to co napisałeś (czyli np.: Show(Form parent); itd.). no i później w każdej wtyczce to musisz zaimplementować.

0
public interface IPlugIn
{
  void ModifyMenu(MainMenu menu);
  // lub
  MenuItem[] PluginMenuItems();

  Form MDIParent {get;set;}
}

Zależy jak duży wpływ chcesz dać wtyczkom na twój program. Bo dając im referencje do menu, mogą rozjechać je na amen (w szczególności usunąć elementy menu, Plik, Zakończ,...).
Dlatego sugeruję metodę nr 2.
Każda twoja wtyczka (klasa opisująca wtyczkę) implementuje ten interfejs. Program podczas ładowania ładuje instancję tej klasy i wywołuje metodę pobierając itemy do menu, a następnie je dodaje. Konkretna implementacja ma także obsługę kliknięcia w MenuItem, więc dokładnie wie co zrobić, czyli odpalić okno potomne. Oczywiście klasa plugIn powinna także dostać referencję do kontenera MDI, przez właściwość.
Następnie możesz mieć kolejny interfejs, który będą implementowały okna wtyczek, który posłuży do komunikacji z tymi oknami. Nie tylko oknu głównemu, ale umożliwi także komunikację między wtyczkami.

public interface IPlugInForm
{
  void SendMessage(Message m);
  PlugInFormFeature[] GetFeatures();
  ...
}
0

Panowie, macie może jakiś kurs, albo ebooka w którym opisano by MEF ? O ile udało mi się otworzyć formę w kontenerze MDI o tyle stwierdzam, że dużo jeszcze nie wiem.

Czy do każdej wtyczki, muszę tworzyć osobny interfejs, w którym będą osobne metody dla poszczególnych wtyczek ?

0

Interfejs jest jeden, tylko dla każdej wtyczki osobna klasa implementująca ten interfejs.

0

Chodzi mi o to, że jak mam obecnie jeden interfejs

namespace MEF_Interfejs
{
    public interface IMessageSender
    {
        bool Show(Form parent);
        bool Send(string tekst);
    }
}

to w każdej wtyczce muszę implementować metody obecne w interfejsie. W przeciwnym wypadku dostaję błąd np.

Error 1 'MEF_Form.formularz' does not implement interface member 'MEF_Interfejs.IMessageSender.Send(string)' D:\Projekty\MEF\MEF_Form\MEF_Form\Form1.cs 15 26 MEF_Form - czyli we wtyczce nie mam metody zdefiniowanej w interfejsie.

Rzecz jasna w różnych wtyczkach, będą różne metody.

Stąd też pytanie, czy mam stworzyć np. jeden interfejs IMessageSender z metodą bool Send(string tekst)
i drugi interfejs np. IFrmShow z metodą bool Show(Form parent) i analogicznie dla innych wtyczek ?

np.

namespace MEF_Interfejs
{
    public interface IMessageSender
    {
        bool Show(Form parent);
        bool Send(string tekst);
    }

    public interface IMsgFrm
    {
        bool Wiadomosc(string tekst);
    }
}

Druga sprawa, to jak załadować wiele wtyczek na raz ? Chodzi mi tutaj o sytuacje gdy N wtyczek korzysta z jednego interfejsu, oraz gdy np. 5 wtyczek korzysta z jednego i 5 z drugiego interfejsu.

0

Wszystkie wtyczki mają implementować (nie korzystać) jeden interfejs. Każda klasa go implementująca będzie mogła być wtyczką do Twojego programu.

0

Cały twój mechanizm wtyczek może wymuszać istnienie klas implementujących wiele interfejsów jakie sobie zdefiniujesz. Czy dziedziczących z klas bazowych, abstrakcyjnych etc.
Ale jak już somekind napisał zbiór twojego "api" dla wszystkich wtyczek jest taki sam. Nie tworzysz dla każdej wtyczki interfejsu, bo jaki to ma sens? Interferjsy tworzy się po to aby uzyskać pewną płaszczyznę abstrakcji, niezależną od implementacji i posługiwać się tą abstrakcją. To jak w ćwiczeniach na dziedziczenie ze zwierzętami, klasa Animal ma dajGlos, ale implementacja w Cat to "miau", a w Dog "hau".
To że w klasie implementującej interfejs musisz zaimplementować wszystkie jego metody itp. to chyba oczywiste.

Jak załadować wiele? Program startuje, lecisz po katalogu z wtyczkami, ładujesz każde assembly i szukasz w nich klas implementujących twój interfejs do inicjalizacji wtyczki (np. IPlugIn). Znajdujesz taką klasę, tworzysz i instancję (np. przez Activator), następnie wywołujesz odpowiednie metody z interfejsu pozwalające załadować w pełni wtyczkę. Składujesz klasę na jakiejś liście, aby poźniej np. zwolić wtyczkę przez wywołanie IPlugIn.Close().

Jeśli chcesz mieć wiele interfejsów to różnych rodzajów wtyczek, to musisz umieć każdy obsłużyć w swojej aplikacji, czyli aplikacja musi wiedzieć że może się jakiegoś interfejsu spodziewać i umieć go obsłużyć kiedy otrzyma taki obiekt. Ale wydaje mi się że napisałeś o tym nie do końca rozumiejąc co chcesz uzyskać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1