Jeder Programmierer kennt das: Man findet im Internet tausende Beispiele für eine neue Programmiersprache/Technologie/Framework, aber wie man eine richtige Anwendung architekturkonform programmiert steht irgendwo-.-”
Mit mehreren Anläufen und viel Erfahrung kann man irgendwann mal von einer guten Architektur reden. Aber Achtung – hier kommt die ultimative Architekturvorlage für JavaFX Anwendungen. Ich zeige in diesem Blogbeitrag, wie man schnell und einfach eine JavaFX Anwendung mit dem MVC Design Pattern programmieren kann. Natürlich kann man diesen Ansatz noch weiter ausbauen, aber die grobe Richtung sollte klar werden.
MVC steht für Model-View-Controller und ist das wohl verbreiteste Design Pattern weltweit. (Bitte nicht mit SOLID-Prinzipen verwechseln!) Das Design Pattern dient zur Strukturierung von Anwendungen und unterteilt diese in 3 Bereiche: dem Datenmodell (engl. model), der Präsentation (engl. view) und der Programmsteuerung (engl. controller).
| View |
|
![]() |
| Controller |
|
|
| Model |
|
Ziel des Model-View-Controller Patterns ist ein flexibler Programmentwurf, der eine spätere Änderung oder Erweiterung erleichtert und eine Wiederverwendbarkeit der einzelnen Komponenten ermöglicht.
Das folgende Beispiel zeigt eine kleine Anwendung, die Namen von AxxG Blog Leser speichert. Auf der linken Seite sieht man eine kleine Übersicht, wie das Java-Projekt aufgebaut ist.
Über die Namensgebung der Packages kann man streiten. Ich bin der Meinung, dass die Sortierung nach Model, View und Controller absolut sinnfrei ist. Der Controller gehört in 95% aller Fälle zu seiner View. Außerdem erleicht diese Art der Sortierung, dass Hin- und Herspringen zwischen den Bereichen.
Wie man sehen kann habe ich einige Namenskonventionen. So enden Controller bei mir mit dem Zusatz “VC“, Views mit dem Zusatz “View” und Models mit dem Zusatz “Bean“.
Einstiegspunkt der Anwendung ist die Start-Klasse. Diese initialisiert das Model (aktuell ein Modell für die gesamte Anwendung) und startet den ersten Controller.
public class Start extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Sessionscope /Applikationscope Beans inizitalisieren!
// muessen von Controllern weitergegeben werden
DataBean dataBean = new DataBean(primaryStage);
// Ersten Controller aufrufen
EingabeVC eingabeVC = new EingabeVC(dataBean);
eingabeVC.show();
}
}
Das Model speichert wie gesagt, alle Daten der Anwendung. In diesem Fall ist das nur die Stage der Anwendung und eine Map mit den Namen.
public class DataBean {
private Stage primaryStage = null;
private Map<String, String> namePwMap = null;
public DataBean(Stage primaryStage) {
this.primaryStage = primaryStage;
this.namePwMap = new HashMap<>();
}
public Map<String, String> getNamePwMap() {
return namePwMap;
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
Zur Kontrolle des MVC Patterns sollte man sich die Imports der Klasse ansehen.
Im Kommentarbereich haben wir bereits eine Diskussion zur Speicherung der Stage geführt. Ein weiterer Lösungsansatz, außer das viewspezifische Model, ist die Speicherung der Stage mit dem Singleton Pattern.
Die EingabeView sieht so (linke Seite) aus und beinhaltet alle Elemente der Oberfläche. Im Konstruktor werden alle Elemente initialisiert, gestylt und gelayoutet. Außerdem werden wichtige Elemente, mit dem der Benutzer interagiert, mit Getter-Funktionen erweitert.
public class EingabeView{
private Scene scene;
private GridPane grid;
private Text scenetitle;
private Label vornameLB;
private TextField vornameTF;
private Label nachnameLB;
private TextField nachnameTF;
private Text meldungT;
private Button okBtn;
private Button addBtn;
private HBox hbBtn;
public EingabeView() {
// Layout
grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
// Ueberschrift
scenetitle = new Text("Hallo AxxG-Leser");
scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));
grid.add(scenetitle, 0, 0, 2, 1);
// Vorname
vornameLB = new Label("Vorname:");
grid.add(vornameLB, 0, 1);
vornameTF = new TextField();
grid.add(vornameTF, 1, 1);
// Nachname
nachnameLB = new Label("Nachname:");
grid.add(nachnameLB, 0, 2);
nachnameTF = new TextField();
grid.add(nachnameTF, 1, 2);
// Buttons
addBtn = new Button("eintragen");
okBtn = new Button("Alle anzeigen");
// Buttongruppe
hbBtn = new HBox(10);
hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
hbBtn.getChildren().add(addBtn);
hbBtn.getChildren().add(okBtn);
grid.add(hbBtn, 1, 4);
// Meldung
meldungT = new Text();
grid.add(meldungT, 1, 6);
scene = new Scene(grid, 300, 300);
}
public void show(Stage stage) {
stage.setTitle("AxxG - JavaFX MVC Beispiel");
stage.setScene(scene);
stage.show();
}
// nur Getter von Elementen anlegen, die veraendert werden und/oder dynamisch sind
public TextField getVornameTF() {
return vornameTF;
}
public TextField getNachnameTF() {
return nachnameTF;
}
public Text getMeldungT() {
return meldungT;
}
public Button getOkBtn() {
return okBtn;
}
public Button getAddBtn() {
return addBtn;
}
}
Der Controller verbindet die View und das Model. Das heißt Werte aus dem Model werden in der View gelesen oder gesetzt. Außerdem werden benötigte Eventhandler registriert und Aufgabe an die ggf. vorhandene Geschäftslogik weitergeleitet! Des Weiteren steuert der Controller, wann und auf welche UI-Maske die Anwendung wechseln soll.
public class EingabeVC {
// Model
private DataBean dataBean;
// View
private EingabeView view;
public EingabeVC(DataBean dataBean) {
this.dataBean = dataBean;
this.view = new EingabeView();
// Eventhandler registrieren
view.getAddBtn().setOnAction(new addBtnEventHandler());
view.getOkBtn().setOnAction(new OkBtnEventHandler());
}
public void show(){
view.show(dataBean.getPrimaryStage());
}
//+++++++++++++++++++++++++++++++++++++++++++++
// Events
//+++++++++++++++++++++++++++++++++++++++++++++
class OkBtnEventHandler implements EventHandler<ActionEvent>{
@Override
public void handle(ActionEvent e) {
// zur naechsten Seiten springen!
AusgabeVC ausgabeVC = new AusgabeVC(dataBean);
ausgabeVC.show();
}
}
class addBtnEventHandler implements EventHandler<ActionEvent>{
@Override
public void handle(ActionEvent e) {
// Meldung reseten
view.getMeldungT().setText("");
// Daten aus den Textfeldern holen
String vname = view.getVornameTF().getText();
String nname = view.getNachnameTF().getText();
//Textfelder zuruecksetzen
view.getNachnameTF().setText("");
view.getVornameTF().setText("");
// Daten testen
if(vname.isEmpty()){
view.getMeldungT().setText("Der Vorname fehlt!");
return;
}
if(nname.isEmpty()){
view.getMeldungT().setText("Der Nachname fehlt!");
return;
}
// Daten hinzufuegen
String erg = null;
erg = dataBean.getNamePwMap().put(nname, vname);
// Meldung ausgeben
if(erg == null){
view.getMeldungT().setText("Leser hinzugefügt");
}else{
view.getMeldungT().setText("Leser bereits vorhanden");
}
}
}
}
Die AusgabeView enthält nur eine Liste und einen Button.
public class AusgabeView{
private Scene scene;
private GridPane grid;
private Text scenetitle;
private ListView<String> list;
private Button backBtn;
private HBox hbBtn;
public AusgabeView() {
// Layout
grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
// Ueberschrift
scenetitle = new Text("AxxG-Leser Übersicht");
scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));
grid.add(scenetitle, 0, 0, 2, 1);
// Liste
list = new ListView<>();
list.setMinWidth(200);
grid.add(list, 0, 1);
// Button
backBtn = new Button("zurück");
// Buttongruppe
hbBtn = new HBox(10);
hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
hbBtn.getChildren().add(backBtn);
grid.add(hbBtn, 0, 2);
scene = new Scene(grid, 300, 300);
}
public void show(Stage stage) {
stage.setTitle("AxxG - JavaFX MVC Beispiel");
stage.setScene(scene);
stage.show();
}
public ListView<String> getList() {
return list;
}
public Button getBackBtn() {
return backBtn;
}
}
Genauso wie der EingabeVC steuert der AusgabeVC den UI-Maskenwechsel.
public class AusgabeVC {
private DataBean dataBean;
private AusgabeView view;
public AusgabeVC(DataBean dataBean) {
this.dataBean = dataBean;
this.view = new AusgabeView();
// Eventhandler registrieren
view.getBackBtn().setOnAction(new backBtnEventHandler());
}
public void show(){
// View mit Daten befuellen
int anz = 1;
for (String key : dataBean.getNamePwMap().keySet()) {
// 1: Name, Vorname
view.getList().getItems().add(anz + ": " + key + ", " + dataBean.getNamePwMap().get(key));
anz++;
}
// View anzeigen
view.show(dataBean.getPrimaryStage());
}
//+++++++++++++++++++++++++++++++++++++++++++++
// Events
//+++++++++++++++++++++++++++++++++++++++++++++
class backBtnEventHandler implements EventHandler<ActionEvent>{
@Override
public void handle(ActionEvent e) {
// zur naechsten Seiten springen!
EingabeVC eingabeVC = new EingabeVC(dataBean);
eingabeVC.show();
}
}
}
Für Faule gibt es hier das ganze Projekt…

Hallo Leser des AxxG Blogs,
ich möchte mich zunächst bei dir bedanken, dass du meinen Beitrag bis zum Ende gelesen hast! Ich hoffe, dass ich dir weiterhelfen konnte^^
Wie du gesehen hast, gebe ich mir sehr viele Mühe bei meinen Beiträgen und beschreibe so gut wie möglich den Sachverhalt. Jedoch kostet das mich sehr viel Zeit und monatlich werden Hosting-Gebühren fällig.
Jetzt aber genug gejammert – Falls dir mein Artikel gefallen hat, kannst du mich ja auf einen Energy-Drink einladen:)
Als Dankeschön erhältst du exklusiv ein zu 100% funktionierendes Projekt und die dazu passende Video-Erklärung!
Lange Zeit sah es sehr sehr sehr dunkel für die Architektur von JavaFX-Anwendungen aus. Niemand hat sich so wirklich für das Thema interessiert. Aber seit der JAX 2013 in Mainz hat sich das Blatt gewendet. Java Größen wie Adam Bien haben sich dem Thema angenommen und entsprechende Lösungen erarbeitet. Hier ist eine kurze Linksammlung:
Copyright © 2013 AxxG – Alexander Gräsel
Ich zitiere mal: “Das Model darf keine Referenz auf eine View oder einen Controller haben!”
Was genau ist dann DataBean#primaryStage, wenn keine Referenz des Models auf eine View? Ich habe jetzt nicht viel Erfahrung mit JavaFX, aber das sieht mir nach einem klaren Verstoß aus.
Hallo Xehpuk,
das ist eine sehr gute Frage, die ich auf Anhieb gar nicht so beantworten kann:-)
Ich gebe dir Recht, dass die primaryStage ein Teil der GUI ist. Um genau zu sein, ist es das Hauptfenster der Anwendung (ähnlich JFrame). Also der graue Rahmen mit den 3 Buttons, Minimieren, Maximieren und Schließen.
In diesem Hauptfenster werden die verschiedenen Views eingesetzt und angezeigt.
Würde man sich nicht die primaryStage im Model merken, würde die Anwendung bei jedem View/Maskenwechsel flakern. Da man bei jedem Wechsel ein neues Fenster erzeugen müsste. Jedoch möchte man dieses Verhalten in der Regel nicht:-)
Leider muss ich xehpuk in diesem Punkt recht geben. Hier wird klar im Model auf die View referenziert, was dem Mvc-Pattern und deiner eigenen Aussage über das MVC-Pattern widerspricht. Meiner Meinung nach also leider, wenn auch gut gemeint, kein gutes Tutorial für MVC mit JavaFx.
Ok, versuchen wir die Schwächen meines Beitrags zu beheben. Hier die Fakten:
- Es gibt eine Stage, die das Hauptfenster der Anwendung repräsentiert
- Man muss sich die Stage irgendwie merken
- Es gibt ein Model, was keine Elemente der View beinhalten darf
Meine Lösungsvorschläge:
- Man verwendet ein zweites viewspezifisches Model und speichert dort die Stage
- Man verwendet das Singleton-Pattern für die Stage
Habt ihr andere Vorschläge?
Hy,
Ich würde das Hauptfenster als Singleton defineren, dann kann man bequem drauf zugreifen und es gibt ja wirklich nur ein Hauptfenster .. also eine Art MainView.getMainStage() ?
Lg Stefan
Hallo,
ich stimme Stefan hier grundsätzlich zu. Allerdings sollte die Hauptklasse (mit main()) kein Singleton sein, da sie einen public constructor ohne Parameter benötigt, sofern sie eine FXML lädt. Sie kann aber eine static Map enthalten, die die Referenzen auf die anderen Stages enthält. Dann flackert auch nix
Hajo