Laden...

Bindungsrichtung, Zugriff auf ViewModel über View

Erstellt von DonStivino vor 8 Jahren Letzter Beitrag vor 8 Jahren 3.356 Views
D
DonStivino Themenstarter:in
51 Beiträge seit 2014
vor 8 Jahren
Bindungsrichtung, Zugriff auf ViewModel über View

Hallo =)

Ich habe folgende Situation:
In meinem MainWindow stelle ich Controls externer Assemblies dar (Plugins). Hierzu binde ich die "Hauptklasse" dieses Plugins, in der sich auch das Control und andere Properties befinden. Aus dem MainViewModel setze ich Properties des Plugins, die dann auch an das ViewModel weitergereicht werden sollen. Da ich das ViewModel im XAML binde komme ich so direkt nicht drauf. Ich möchte auch das ViewModel nicht als Property des Plugins kennen, da ich das unsauber finde.

Meine Lösung wäre nun so was:

 
// In der Klasse Plugin
PluginWorkspace view = new PluginWorkspace();     // Control
PluginWorkspaceViewModel viewModel = view.DataContext as PluginWorkspaceViewModel;    // ViewModel
if (viewModel != null) {
                // Properties zugreifen
                viewModel.Plugin = this;
}


Ich habe die Situation mal noch versucht in einem Klassendiagramm darzustellen. (siehe Anhang)

Für mich als relativ unerfahrenen Programmierer wirkt das aber sehr unsauber. Was meint ihr? Ist das eine gängige und sichere Lösung? Widerspricht das dem MVVM-Konzept? Gibt es bessere Lösungen?

Vielen Dank schon mal!

Viele Grüße

S

P
157 Beiträge seit 2014
vor 8 Jahren

Holla,

da du irgendwo View und ViewModel verbinden musst, ist eine Plugin-Assembly schon die richtige Stelle. Ein Plugin ist sowas wie eine zusätzliche "void main" methode, in der du zusätzliche Daten und Funktionen bereit stellen kannst.

Hier ein paar kleine Hinweise, wenn du das etwas ausbauen möchtest:

Zum einen, wenn du Plugins verwenden möchtest, mach plugins draus 😉 Wenn du referenzierst, hast du kein Plugin mehr. Dafür gibt es IOC-Frameworks. Die sind etwas gewöhnungsbedürftig, aber machen eine Anwendung enorm flexibel...besser n Framework verwenden, als selbst zu schreiben, ausser du hast ein paar wochen Zeit...autofac lässt sich sehr einfach verwenden.

Vermeide solche Konstrukte:

viewModel.Plugin = this;

Ist einfacher nur eine Richtung,und zwar von oben nach unten, zu verwalten. Du kannst mit dem IOC-Framework auch vorhandene Instanzen an einer beliebigen Stelle abgreifen - alternativ einen ServiceLocator verwenden...->ServiceLocator.Get<IMyPlugin>()...oder ObjectFactory.Get<IMyPlugin> ... solche Aufrufe sind wesentlich einfacher, als Instanzen quer durch die Anwendung weiter zu reichen.

Man muss ein wenig Erfahrung mit sowas sammeln, aber ist alles kein Hexenwerk.

Wenn du das richtig zusammenschusterst, hast du am Ende eine Exe ohne Inhalt die aus einer Konfiguration den Inhalt zusammenstellt...

vg

Wenn's zum weinen nicht reicht, lach drüber!

D
DonStivino Themenstarter:in
51 Beiträge seit 2014
vor 8 Jahren

Hallo Parso,

danke für die schnelle Antwort!

Zum einen, wenn du Plugins verwenden möchtest, mach plugins draus 😉 Wenn du referenzierst, hast du kein Plugin mehr. Dafür gibt es IOC-Frameworks. Die sind etwas gewöhnungsbedürftig, aber machen eine Anwendung enorm flexibel...besser n Framework verwenden, als selbst zu schreiben, ausser du hast ein paar wochen Zeit...autofac lässt sich sehr einfach verwenden.
vg

Die Plugins selbst lade ich durch Reflektion von Assemblies, die in einer Konfigurationsdatei abgelegt sind. Die Anwendung selbst ist völlig losgeslöst von den Plugins. Diese werden entweder beim Start der Laufzeit geladen oder eben nicht.

Du bringst mich damit auf eine Idee: Theoretisch könnte ich den Workspace selbst zusammen mit allem anderen auch in einem eigenen Konstrukt speichern. Ein neues Objekt quasi. Dann könnte ich die Bindung entsprechend deiner Kritik in nur eine Richtung ausprägen. Dadurch hätte ich konzeptionell aber sicher noch einiges zu tun...

Zum einen, wenn du Plugins verwenden möchtest, mach plugins draus 😉 Wenn du referenzierst, hast du kein Plugin mehr. Dafür gibt es IOC-Frameworks. Die sind etwas gewöhnungsbedürftig, aber machen eine Anwendung enorm flexibel...besser n Framework verwenden, als selbst zu schreiben, ausser du hast ein paar wochen Zeit...autofac lässt sich sehr einfach verwenden.
vg

Davon habe ich noch nie gehört aber wie gesagt ich bin auch sehr unerfahren in der Hinsicht. Da muss ich mich wohl mal informieren.

Viele Grüße

S

P
157 Beiträge seit 2014
vor 8 Jahren

Ah ich hatte vermutet, dass du die referenziert fest eingebunden hast. ich hab schon ein wenig erfahrung in der entwicklung von IOC-Framework-Sachen, die Grundidee ist einfach, aber der Teufel steckt wirklich im Detail, irgendwann musst du mal deine Typen Instanzieren, dann möchtest du verschiedene Instanzierung-Verhaltensweisen, dann Injection, dann noch ein paar delegaten beim instanzieren ausführen...hach da bist ne weile mit beschäftigt 😉 Ist ganz interessant sowas als Forschungsprojekt zu machen, aber wenns eilig ist, sollte man das nicht tun, Die MS - Doku ist ... lückenhaft 😉

Du bringst mich damit auf eine Idee: Theoretisch könnte ich den Workspace selbst zusammen mit allem anderen auch in einem eigenen Konstrukt speichern. Ein neues Objekt quasi. Dann könnte ich die Bindung entsprechend deiner Kritik in nur eine Richtung ausprägen. Dadurch hätte ich konzeptionell aber sicher noch einiges zu tun...

Hier hast du nen Keks 😉 So könnte man es ausversehen machen...wenn du es jetzt noch schaffst kein "new ()" für deine ganzen einstiegsobjekte zu verwenden (das macht das IOC Framework für dich), bist du schon auf nem guten weg.

Wenn's zum weinen nicht reicht, lach drüber!

D
DonStivino Themenstarter:in
51 Beiträge seit 2014
vor 8 Jahren

Hier hast du nen Keks 😉 So könnte man es ausversehen machen...wenn du es jetzt noch schaffst kein "new ()" für deine ganzen einstiegsobjekte zu verwenden (das macht das IOC Framework für dich), bist du schon auf nem guten weg.

Die Reflektion kommt ja ohne Allokation durch new() aus. Wie das dann technisch genau läuft ist natürlich was anderes aber da reflektiere ich durch so was hier:


Assembly assembly = null;
assembly = Assembly.LoadFile(pluginFullPath);
if (assembly != null) {
IPlugin plugin = null;
                    foreach (Type type in assembly.GetExportedTypes()) {
                        if (typeof(IPlugin).IsAssignableFrom(type)) {
                            plugin = ((IPlugin)Activator.CreateInstance(type));
                        }
                    }

Dort müsste ich dann auch die Properties setzen... oder? Aber da ist so ein IOC Framework cleverer nehme ich an?

P
157 Beiträge seit 2014
vor 8 Jahren

Jups, so isses richtig, nur was machst du wenn einer deiner Typen einen andere verwendet ?

Also Typ A wird instanziert und verwendet Typ B ... dann hast du erstmal ein Problem. Es ist ratsam den Activator in eine Klasse zu kapseln und erst beim Zugriff zu instanzieren.

Wenn's zum weinen nicht reicht, lach drüber!

D
DonStivino Themenstarter:in
51 Beiträge seit 2014
vor 8 Jahren

Jups, so isses richtig, nur was machst du wenn einer deiner Typen einen andere verwendet ?

Die Plugins müssen prinzipiell ein Interface implementieren, insbesondere damit sie Properties zur Darstellung implemtieren, so wie verschiedene Methoden etc. Das ist einfach Voraussetzung. Da die Plugins selbst auch nochmal durch Addons erweiterbar sind, müssen diese auch durch den Plugin bereitgestellt werden (wieder vor allem wegen der Darstellung). Die Addons müssen auch wieder ein Interface implementieren.

Es ist ratsam den Activator in eine Klasse zu kapseln und erst beim Zugriff zu instanzieren.

Da habe ich einen PluginManager für. Der ist statisch und kümmert sich um das Laden der Plugins, um den Aufruf von Methoden zum initialisieren und andere Verwaltungsaufgaben wie die Konfiguration neuer Plugins durch den Benutzer zur Laufzeit und so was alles 🙂

Viele Grüße

S

P
157 Beiträge seit 2014
vor 8 Jahren

Vielleicht mal anders ausgedrückt:

Assembly A - Typ A

Assembly B - Typ B

Container enthält deine geladenen Typen (plugins, etc)


public class A 
{
   public class A()
   {
       B  = Container.Get<B>();
   }
   public B {get;set;}
}

B = Container.Get<B>(); <<--- B existiert nicht weil B noch nicht geladen wurde, also der Typ unbekannt ist, auch wenn du dort interfaces dran machst, kann das interface nur ein Schlüssel sein um an B zu kommen, nicht aber B selbst...kausalität...anders ausgedrückt: geht nicht, weil ist nicht da.

Nun kannst du die Reihenfolge deiner Assemblies in der Konfig ändern, aber das funktioniert auch nur eine Weile, bis irgendwas gemacht werden soll, wo die Reihenfolge nicht mehr reicht.

Es wird immer eine Komponente geben, die andere verwendet, im schlimmsten Fall über Kreuz, das tut nicht weh und sollte auch funktionieren(solange man in der selben "Architektur-Schicht" ist)

Das Laden von Assemblies und Typen ist leicht, die untereinander zu verbinden, eine andere Geschichte, wie gesagt, der Teufel liegt im Detail.

Wenn's zum weinen nicht reicht, lach drüber!

D
DonStivino Themenstarter:in
51 Beiträge seit 2014
vor 8 Jahren

Da sprichst du langfristig vielleicht schon ein wesentliches Problem an, stimmt. Ich schaue mir mal das IOC an, welches du empfohlen hast.

Vielen Dank für den Tipp!