JavaFX – Verknüpfung von FXML und Quellcode

fxml Pojo

Mit dem Release von Java 8 wurde von Oracle ein weiterer Schritt in Richtung Ablösung von Swing durch JavaFX getan. JavaFX ist nun unter anderem im Classpath eingetragen. Entwickler müssen sich nun nicht mehr selbst um die Einbindung in Projekte kümmern.

Mit der Version 2 wurde das aus WPF (Windows Presentation Foundation) bekannte Modell der Dreiteilung in Layout, Styleheet und Quellcode in JavaFX übernommen.

fxml Pojo

Dieser Ansatz ist zwar zu empfehlen, es können aber auch Anwendungen rein programmatisch erstellt werden. In diesem Blog soll kurz vorgestellt werden, wie das Layout (FXML) mit dem Quellcode verbunden wird.

Die FXML-Datei beinhaltet das komplette Layout und kann für jeweils eine sogenannte Scene verwendet werden. Bei einer Scene handelt es sich um den Inhalt eines Fensters. Ein Fenster wird in JavaFX als Stage bezeichnet. Die Scene ist ein Container für einen Objektgraphen, welcher die UI-Elemente repräsentiert. Für die Benutzeroberfläche gilt, dass alle Elemente in einem Baum mit genau einem Wurzelknoten organisiert sein müssen.

Verwenden wir FXML, so modellieren wird jenen Objektgraphen. Für die Realisierung von Funktionalitäten werden sogenannte Controller verwendet, welche im Wurzelelement definiert werden. Sie implementieren die Eventhandler, welche in einem jeweiligen Knoten des Graphen definiert werden und können auf die Elemente des Graphens zugreifen, wenn jenen Elementen eine ID zugewiesen worden ist.

In unserem Beispiel verwenden wir den Oracle Scenebuilder, um das Layout zu erstellen.

Oracle Scenebuilder

Oracle Scenebuilder

Per Drag & Drop können wir die Oberfläche mit wenigen Klicks zusammenstellen. Unter dem Reiter controls werden dann für die TextArea, für den Button und für das Label IDs vergeben.

Text Area

Die Abbildung zeigt, wie unsere Testanwendung aussehen wird. Wir verwenden eine TextArea, um einen beliebigen Text einzugeben. Der Button fügt diesen Text in das Label ein und leert die TextArea.

<?xml version="1.0" encoding="UTF-8"?>

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
 
public class FXML_Example extends Application{
 
	public static void main(String[] args){
		launch(args);
	}
 
	public void start(Stage stage) throws Exception {
		Pane root = (Pane) FXMLLoader.load(getClass().getResource("testFXML.fxml"));
		Scene scene = new Scene(root);
		stage.setScene(scene);
		stage.show();
	}
}

Wir legen eine Klasse FXML_Example an, welche von Application erbt. Dies brauchen wir, um die Laufzeitumgebung von JavaFX zu laden. Was für uns von Interesse ist, ist die erste Zeile in der Methode start(). Über den FXMLLoader wird das Layout geladen. Der Loader übersetzt die XML-Datei direkt in einen Objektgraphen und übergibt das Wurzelelement. Dabei wird auch direkt der verwendete Controller geladen.

package de.essential_bytes.blog.fxml_example.main;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;

public class MyController {
	

	@FXML
	TextArea textArea;
	
	@FXML
	Label label;
	
	@FXML
	protected void buttonPressed(){
		String text = textArea.getText();
		label.setText(text);
		textArea.clear();
	}
}

Der Controller MyController muss kein explizites Interface implementieren oder von einer Klasse erben. Bei der Gestaltung des Controllers herrschen alle Freiheiten. Wichtig ist nur, dass alle EventHandler, die in der FXML-Datei definiert wurden, implementiert werden. Dafür gelten zwei Vorraussetzungen:

  • Sowohl für die Elemente, auf die der Controller zugreifen soll, als auch für die Implementierungen von Handler muss eine @FXML-Annotation verwendet werden.
  • Die Attribute und Handler müssen so benannt werden wie ihr Äquivalent in der FXML-Datei.

Für den Button brauchen wir in diesem Beispiel kein Attribut anzulegen, da wir nicht auf das Element selbst zugreifen wollen sondern den definierten Handler implementieren. In der Methode buttonPressed() wird der String aus der TextArea gelesen und als Text für das Label gesetzt.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" 
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.essential_bytes.blog.fxml_example.main.MyController">
   <children>
      <Button fx:id="button" layoutX="19.0" layoutY="217.0" mnemonicParsing="false" 
      	onAction="#buttonPressed" prefHeight="25.0" prefWidth="550.0" text="moveOver" />
      <MenuBar layoutY="2.0" prefHeight="25.0" prefWidth="600.0">
        <menus>
          <Menu mnemonicParsing="false" text="File">
            <items>
              <MenuItem id="exit" fx:id="exit" mnemonicParsing="false" text="Close" />
            </items>
          </Menu>
          <Menu mnemonicParsing="false" text="Edit">
            <items>
              <MenuItem mnemonicParsing="false" text="Delete" />
            </items>
          </Menu>
          <Menu mnemonicParsing="false" text="Help">
            <items>
              <MenuItem mnemonicParsing="false" text="About" />
            </items>
          </Menu>
        </menus>
      </MenuBar>
      <Label fx:id="label" layoutX="19.0" layoutY="252.0" prefHeight="134.0" prefWidth="550.0" text="Label" />
      <TextArea fx:id="textArea" layoutX="19.0" layoutY="42.0" prefHeight="161.0" prefWidth="550.0" />
   </children>
</Pane>

Die Abbildung zeigt die FXML-Datei. Wichtig hierbei ist, dass der Controller im Wurzelelement eingetragen wird. Hierbei muss der komplette Classpath angegeben werden. Da JavaFX auch CSS kennt, wird in dem ersten Element vom Typ MenuItem der Umgang verdeutlicht. Es gibt Attribute mit und ohne fx-Präfix. In diesem Fall sind das die Attribute id und fx:id. Diese Unterscheidung ist wichtig, da es sich bei id um den CSS-Identifier handelt und um fx:id hingegen um den Java-Identifier, den wir für unseren Controller benötigen. Unser Controller reagiert auf das Attribut fx:id.

Beispielanwendung

Starten wir die Anwendung schaut das Resultat so aus. Wir könnten das komplette Layout mit dem Scenebuilder erstellen und in die FXML-Datei auslagern. Wir haben insgesamt nur 12 Zeilen Code für den Controller und eine Zeile Code für das Laden der FXML-Datei benötigt. Alles andere hat die Laufzeitumgebung von JavaFX für uns übernommen. Wir sehen also, dass wir durch die Verwendung von FMXL nicht nur eine saubere Trennung haben, durch die parallel an verschiedenen Stellen gearbeitet werden kann (es können gleichzeitig die Oberfläche gestaltet und die Logik implementiert werden), wir können uns auch viel Arbeit ersparen, da sich mit dem Scenebuilder von Oracle Oberflächen sehr leicht erstellen lassen.