Laden...

Pluginsystem mit GUI

Erstellt von ThomasR vor 15 Jahren Letzter Beitrag vor 15 Jahren 2.302 Views
T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren
Pluginsystem mit GUI

Hallo,

ich bin gerade dabei, mir für meine Anwendung eine saubere Plugin-Architektur auszudenken und teste da ein wenig rum.

Ich habe mir mal ein System nach diesem Vorbild aufgebaut: PluginsInCSharp auf Codeprojekt

Das klappt auch wunderbar, nur eine Sache will mir daran nicht so recht gefallen und dazu würde mich eure Meinung interessieren bzw. ob ihr andere Wege kennt:

Jedes Plugin besitzt dort eine Klasse, welches das Plugininterface implementiert. Wenn Forms verwendet werden, wird in dieser Klasse das Form-Objekt deklariert.
Und hier ist mein Problem: Normalerweise gehe ich aufgrund einer sauberen Trennung von Gui und Businessklassen genau den umgekehrten Weg: Die Form kennt die Businessklasse aber die Businessklasse kennt die Form eben nicht!

Im Konkreten Fall soll ein Plugin bei mir aus zwei Forms / oder Usercontrols (ist ja egal) und mindestens einer Businessklasse bestehen. Ein Form stellt die Oberfläche des Plugins dar, ein anderes Form ein Konfigurationsmenü des Plugins und die Businessschicht eben die Funktionalität (hier ist auch Datenaustausch zwischen Plugin und Hauptanwendung erforderlich)

ich hoffe ich habe meine Bedenken gegenüber der Vorgehensweise auf Codeprojekt verständlich rübergebracht!

Gruß
Thomas

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo ThomasR,

du hast ganz Recht. Gui und Businessklassen sollten sauber getrennt sein.

In Generic Manipulator Tool findest du so eine Trennung realisiert. Daran kannst du dich orientieren.

herbivore

T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren

Hallo herbivore,

natürlich habe ich mich (vorbildlich wie ich bin 😄) hier im Forum schon eine lange Zeit nach dem Thema umgeschaut, daher kenne ich auch das Generic Manipulator Tool schon. Leider hilft mir das in diesem Fall nicht weiter, da die Plugins dort keine eigene GUI/Forms mitbringen sondern nur aus jeweils einer einzigen Klasse bestehen.

Gruß
Thomas

915 Beiträge seit 2006
vor 15 Jahren

Hrm, der einfachste Weg um das zu realisieren wäre eine Trennung auch zwischen den Schnittstellen bzw. Interfaces gekoppelt mit Factory.

Das ganze habe ich damals bei meinen ersten Pluginprojekt wie folgt gelöst um auch später ein InternetLive Update zu realisieren:

  • Das erste Projekt beinhaltet nur eine Schnittstelle mit ein Proeprties, wie Version, Produktnummer alternativ kann diese auch leer sein - sie muss nur exestieren.
  • Das zweite Projekt ist eine Exe Anwendung welche die referenz vom ertsen Projekt beinhaltet.
  • Das dritte Projekt ist die Komunikationsbasisschicht und Hauptanwendung, diese wird vom zweiten Projekt geladen und zwar über die Schnittstelle des ersten Projekts.
  • Das zweite Projekt bekommt nun einen InternetLiveUpdater oder normalen Updater, ebenso da es ja die Hauptanendungs Dll also das dritte Projekt ladet über Assembly.LoadFrom soll diese auch wieder entladen können. Somit kann man die Hauptanwendung jederzeit umtasuchen.

Soo und nun gehts los, die Hauptanwendung also das dritte Projekt dient dazu Plugins mit Informationen wie deren Schnittstellenimplementation bereitzustellen. So wie Codeprojekt es anbietet nur etwas aufgedröselter. Dafür braucht man eine Klasse zum Laden und entladen der Module, quasi fast das selbe wie im zweiten Projekt. Um die Plugins zu Identifizieren benötigt es verschiedene Schnittstellen: IPlugin - beinhaltet Properties wie Version aber vorallem Dependencies, eine Liste von abhängigkeiten - den das Plugin kann ja andere Plugings als vorraussetzung benötigen, ebenso kann man noch andere wichtige implementationen in die Shcnittstelle mitreinbringen, eine davon wäre die bereitstellung der vorhandenen Liste von IForm's. IForm ist weiderum eine Schnittstelle welche Grundlegende Methoden von System.Windows.Form implementiert - also Methoden wie Show Hide und Properties wie Visible usw. Die nächste Schnittstelle ist für Plugins der Sorte IBusinessObject - diese besonderen Objecte bilden die BusinessLayer ab. Somit kann ein Plugin IForm's und IBusinessObjecte beinhalten, oder nur IBusinessObjecte oder nur IForm's. Das ist wichtig falls jemand externes irgendwann mal anbindungen an dein Modul schreiben möchte.

Soo das war der leichte Teil, die Komunikationsschicht ist ja das A und O eines Pluginsystems. Das wichtigste ist eine efiziente Fehlerfindung bei Komunikationsfehlern. Hierzu implementiere eine Singletonklasse in das dritte Projekt. In der Singletonklasse stellst du ein Delegate das z.B. über ein Proeprtie ansprichst bereit - sprich du zeigst auf den Instanzierten Handler nicht aber auf das Event. Intern stellst du eine private event mit dem Scope auf das delegate bereit. Soo was will man damit ereichen? Ganz einfach, man nutzt eine Intiligente Komposition als Informationsträger, diese realisiert man über das Command Patern. Hierzu kannst du eine Schnittstelle schreiben die zu den selben Schnittstellen gepackt werden wo sich auch IForm und IBusinessObject und IPlugin befindet, diese nennst du ICommand. ICommand beinhaltet nur ein Property Type und eine Methode void Execute, später kannst noch Undo, Redo hinzufügen aber ist erstmal nicht wichtig. Jetzt erstellst du eine Basisklasse (abstract) welche die Schnittstelle ICommand beinhaltet. Davon abgeleitet erstellst du z.B. folgende Klassen: FormAction, MenuAction, PluginAction und später evtl noch nen paar mehr. Die Singletonklasse mit dem delegate feuert und bearbeitet diese Klassen und zwar über die Schnittstelle ICommand. Was bedeutet das? - Ganz einfach, die ePlugins referenzieren auf das dritte Projekt diese Hauptanwendung und wenn etwas z.B. bei deiner Form passieren soll das diese angezeigt werdne soll oder einen Menüeintrag hinzufügen oder löschen soll so erstellst du eine diese Actions wie ForAction und oder MenuAction und übergibst diese an das Delegate der Singletonklasse. Und voila, du hast eine getrennte Schicht. Was ist nun mit den BusinessObjecten in den Forms? Diese Ladest du ja in die generische Liste im PluginLoader und stellst diesen Loader ebenso über die Singletonklasse zur verfügung, somit kannst du über die Schnittstelle IBusinessObject mit angabe des entsprechenden Plugins z.B. über dessen festen Namen als string bereit.

So, das Protokolieren und Fehlerfinden von Komunikationsfehlern zwische den Plugins ist dann das einfachste der wellt in dem Event der Singletonklasse verarbeitet und vershcickst du ja die Nachrichten von Plugin A auch an Plugin B ohne das diese sich überhaupt kennen, hier kannst du auch ein try catch einbauen und Fehler die erzeugt werdne abfangen und Protokollieren. Und besser noch, du kannst sogar alle Nachrichten mitloggen selbst jene die keinen Fehler erzeugten.

Hoffe das dir das weiterhilft .-)

Stichworte: Plugin, CommandPattern, Singleton, BusinessObjects, Actions

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo ThomasR,

Leider hilft mir das in diesem Fall nicht weiter, da die Plugins dort keine eigene GUI/Forms mitbringen sondern nur aus jeweils einer einzigen Klasse bestehen.

Das ist doch aber genau was du willst. Das Plugin soll ja wegen der Trennung von GUI und Modell gerade kein GUI enthalten. Es muss aber natürlich stattdessen alle Informationen anbieten, die das GUI braucht, um das Plugin grafisch darzustellen. Beim Generic Manipulator Tool wird das inbesondere bei der Konfiguration klar. Das Plugin bringt zwar keinen Konfigurationsdialog mit, bestimmt aber trotzdem, was man konfigurieren kann. Es bietet also alle Informationen an, damit das Host-Programm daraus einen Konfigurationsdialog aufbauen kann. Insofern hilft dir das Generic Manipulator Tool auch in deinem Fall weiter.

Dass die Plugins dort keine eigene GUI/Forms mitbringen, sondern nur aus jeweils einer einzigen Klasse bestehen ist gerade die Lösung deines Problems!

herbivore

T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren

der einfachste Weg...uiuiui 8o

danke Andreas.May für deine Antwort. Bis ich das durchschaut habe werde ich deinen Text allerdings noch 5 - 100 mal lesen müssen 😉

T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren

Das ist doch aber genau was du willst. Das Plugin soll ja wegen der Trennung von GUI und Modell gerade kein GUI enthalten

Doch soll es. Das Plugin soll beides beinhalten, aber eben so dass beides auch im Plugin sauber getrennt ist. Das Konzept vom GM hilft mir nichts, weil ich in der Forms-Gestaltung der Plugins flexibler sein muss.

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo ThomasR,

und was machst du, wenn das Host-Programm später ein anderes GUI bedienen soll (WPF, Web-Anwendung, ...)? Dann nützt dir die saubere Trennung innerhalb des Plugins gar nichts. Gewonnen hast du erst, wenn das Plugin gar kein Windows.Forms mehr enthält.

Ich sage mal so: Wenn klar ist, dass die Host-Anwendung immer Windows.Forms ist, kannst du dir die Trennung von GUI und Modell (etwas überspitzt formuliert) gleich sparen. Wenn jedoch klar ist, dass die Host-Anwendung nicht immer Windows.Forms sein wird, dann nützt dir eine reine Trennung nichts, sondern nur der Verzicht auf Windows-Forms im Plugin. Genau das macht das Generic Manipulator Tool.

Wie flexibel die "Forms"-Gestaltung ist, hängt davon ab, was du im Host-Programm bzw. im Interface des Plugins an Gestaltungsmöglichkeiten vorsiehst.

herbivore

T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren

ok, also mein zweck ist es nicht die Trennung bis in den Exzess zu betreiben. Es ist auch definitiv nicht zu erwarten, dass die Anwendung mal als Web oder WPF laufen soll. (Und wenn, dann müssen eben auch die Plugins (bzw. eben nach möglichkeit nur deren GUI) geändert werden.

Mein Ziel ist es eigentlich nur, "sauberen" Code zu schreiben. Und da ist mir eben die beidseitige Navigierbarkeit zwischen Form und Logikklasse ein bisschen ein Dorn im Auge...

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo ThomasR,

wenn du also sowieso vorhast, Forms zu schreiben, was hindert dich dann daran die Zugriffsrichtung umzudrehen? Also so dass die Forms auf das Modell zugreifen und das Modell das Form nicht kennt?

herbivore

T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren

wenn du also sowieso vorhast, Forms zu schreiben, was hindert dich dann daran die Zugriffsrichtung umzudrehen? Also so dass die Forms auf das Modell zugreifen und das Modell das Form nicht kennt?

daran hindert mich, dass dann ja die ganze Kommunikation zur Host-Anwendung über die Form läuft... ich hoffe ich habe dich jetzt richtig verstanden...du meinst dann also dass die Form das IPlugin implementiert?

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo ThomasR,

du meinst dann also dass die Form das IPlugin implementiert?

ja!

Oder von mir aus auch zwei bzw. drei Plugin-Interfaces für Form, Konfiguration und Modell.

herbivore

T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren

Hallo herbivore,

3 Interfaces waren auch mein erster Gedanke, allerdings weiß ich noch nicht so recht wie ich die dann unter einen Hut bringe...könntest du bitte kurz erläutern, wie du dir das vorstellst?

Danke
ThomasR

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo ThomasR,

da gibt es verschiedene Möglichkeiten.

Es kommt z.B. darauf an, wer die Business-Objekte erzeugen soll bzw. kann. Wenn das Form.Objekt dazu in der Lage ist, dann würde u.U. ein Interface reichen. Das Form-Objekt würde dann das Business-Objekt und den Konfigurationsdialog erzeugen und selbst verwenden. Alle Zugriffe würde über das Form-Objekt gehen.

Natürlich könnte könnte das Form-Objekt (respektive das Plugin-Interface) auch zwei Methoden haben, damit man direkten Zugriff auf Business-Objekt und Konfigurationsdialog bekommt.

public IBusinessObject GetBusinessObject ()
public IConfigurationDialog GetConfigurationDialog ()

herbivore

T
ThomasR Themenstarter:in
93 Beiträge seit 2007
vor 15 Jahren

dankeschön!
ich werde die Sache nochmal überdenken und mich dann für die in meinen Augen sauberste Lösung entscheiden.

F
10.010 Beiträge seit 2004
vor 15 Jahren

Oder mal CAB/SCSF anschauen.

Hier ist das alles schon vorexerziert.

Pluginsystem, MVP/MVC pattern, Eventmachanismus, IOC usw.

Und kostenlos, im Source und mit einer immer grösser werdenden gemeinde.