MVVM ist aus meiner Sicht für Desktop Anwendungen mit die beste Lösung, da die UI deklarativ erstellt wird und damit gut vom Model getrennt ist. Hier war in der Vergangenheit die Library mvmFX eine sehr gute Unterstützung, jedoch hat diese zuletzt vor einigen Jahren ein Update bekommen, so dass eine produktive Nutzung eher problematisch ist.
Daher macht es Sinn, einfach einmal zu schauen, wie eine MVVM Anwendung prinzipiell aufgebaut ist und wie man hier ggf. mit ein paar wenigen Klassen eine deutliche Vereinfachung herbeiführen kann.
Aufbau MVVM
Model
Wir haben eine Datenklasse mit unseren Daten. Dazu bauen wir uns einfach einmal eine kleine Klasse User mit username und email:
Wenn wir nun eine Instanz dieser Klasse User anzeigen lassen wollen, brauchen wir eine Oberfläche, die wir per fxml deklarativ darstellen können. In der Oberfläche möchten wir dann username und email sehen und ändern können und wir haben eine einfache Aktion, die wir hier einfach einmal als Speichern aufgeführt haben.
Zur Verbindung benötigen wir nun noch ein ViewModel. Dieses kennt das Model und bietet die Daten der View über Properties an. Weiterhin kennt es mögliche Aktionen, bei uns das Speichern:
Nun müssen die View und das ViewModel verbunden werden. Dazu ist in der View ein Controller angegeben. Dieser kenn das ViewModel und die View und kann daher die Elemente verbinden.
Dies zeigt den einfachen Aufbau von MVVM sowie die grosse Masse an Code, die hier geschrieben werden muss, nur um dies umzusetzen:
Eine zweite Version des Models, das alle Attribute enthält und das darüber hinaus noch weiteren Code (wie z.B. die save Methode) enthält.
Ein Controller nur für das Binding
Code, den wir nicht gezeigt haben: Nach dem Laden des FXML muss im Anschluss noch im Controller das ViewModel gesetzt werden.
Das ist nicht nur zusätzliche Arbeit sondern zugleich auch umständlich zu warten und anzupassen. Was wir also benötigen, ist eine Vereinfachung.
Ideen zur Vereinfachung
Model
Das ist unsere Datenklasse. Die wollen wir natürlich prinzipiell unverändert lassen. Eine Option, die wir hier ins Auge fassen können: Erweiterung des Models durch Annotations
ViewModel
Das ist eine Klasse, wie wir automatisiert aus dem Model erzeugen können:
Alle Datenfelder werden zu entsprechenden Properties.
Mögliche Angabe von Commands. Damit können wir Aktionen binden wie z.B. ein Knopfdruck.
Controller
Der Controller hat in erster Linie eine Aufgabe: Das Binding zwischen View und ViewModel. Dieses Binding können wir auch in unser FXML aufnehmen und hier das Binding direkt angeben.
View
Diese erstellen wir erst einmal weiter und ergänzen diese mit den Bindings.
Für Formulare und ähnlichen Standard Anwendungen können wir aber auch überlegen, ob man dies nicht auch generieren kann um dann die notwendigen Angaben in das Model fliessen zu lassen (als ein weiterer Ausbau).
Automatische Erstellung des ViewModels
Ein erstes, einfaches, automatisches ViewModel könnte so aussehen:
Es ist eine generische Klasse für ein Model vom Typ T
Es speichert das Model in sich
Es hat eine Map von Properties: Der Name der Property hin zu der Property
Im Konstruktor wird dann das Model übergeben. Dabei wird dieses ausgewertet per Reflection und alle notwendigen Properties erstellt und in einer Map gespeichert.
Aber das Problem wird schnell offensichtlich: Wir haben jetzt zwar unsere Properties, aber damit können wir nicht direkt etwas anfangen. In unserem Beispiel klappt es, da wir Strings haben und auc die View mit StringProperties arbeitet. Aber nehmen wir nur ein weiteres Attribut, wie alter (int) oder ein Zeitstempel (Instant). Diese könnten wir auch in einem Textfeld darstellen wollen nur das hast eine StringProperty und wir haben IntegerProperty oder ObjectProperty. Das lässt sich so nicht direkt binden. Statt dessen brauchen wir zusätzlich Converter, die dann z.B. einen Instant entsprechend darstellen oder eben eine Eingabe in einen Instant umwandelt. Diese Problematik würde entweder direkt in dem ViewModel oder im Controller beim Binding gelöst.
Da es vom verwendeten Control abhängen kann, was benötigt wird und die View angepasst werden können soll, ohne das ViewModel zu ändern, werden wir diese Thematik im Binding angehen.
Anpassung des Bindings
Um Bindings vornehmen zu können, können wir nun ein neues Control Bindings erstellen. Dieses bekommt dann alle Informationen, die notwendig sind, um das Binding durchzuführen:
Source: Was soll genau gebunden werden?
Target: Was ist das Ziel des Bindings?
Richtung: Ist es nur in eine Richtung oder geht das Binding in beide Richtungen
Converters: Was für Converter sind für ein Binding notwendig?