Was ist und wozu dient FXML?
v.8.0JavaFX ist ein Framework, das in einigen Bereichen durchaus geeignet sein könnte, die Nachfolge der erfolgreichen Swing-Bibliothek anzutreten. Seine Anwendung folgt dem Model-View-Controller-Entwurfsmuster (MVC), sodass graphische Oberflächen von den Rechenroutinen und vom Datenpool getrennt bleiben.
Diese Trennung erlaubt es, die Entwicklung einer graphischen
Nutzeroberfläche weitgehend vom Rest des Programmentwurfs zu
entkoppeln. In einer *.fxml
-Datei kann hierbei der
vollständige Baum aller graphischen Objekte festgelegt werden.
Dies kann sowohl in einem XML- oder einfachen Texteditor erfolgen,
als auch im JavaFX Scene Builder, einem hierfür
speziell entwickelten WYSIWYG-Editor1.
Die
Gestaltung einer Nutzeroberfläche mit Hilfe von FXML bietet
hierbei die folgenden Vorteile:
- Da in der Datei der gesamte scene graph aller graphischen Objekte in Baumform festgelegt ist, kann er i.A. als Ganzes überblickt und hierdurch besser gewartet werden.
- Sprachanpassungen (localization) gestalten sich einfacher.
- FXML kann mit jeder JVM-Sprache angewendet werden.
- FXML ist nicht auf die Erstellung von GUI beschränkt und kann durch Script-Sprachen erweitert werden.
Das hier behandelte Beispiel zeigt eine einfache Anwendung, die, nach Klick auf einen Button, einen Text auf einem Label zeigt.
Die konventionelle prozedurale Variante
Wird die Oberfläche konventionell programmiert, so könnte der Quelltext etwa folgendermaßen aussehen:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class FXMLBsp extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
GridPane root = new GridPane();
Button button = new Button("Klick");
button.setPrefWidth(100.0);
root.add(button,0,0 );
Label label = new Label();
label.setPrefWidth(100.0);
label.setAlignment(Pos.CENTER);
root.add(label,0,1 );
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
label.setText(new FXMLBspModel().getHello());
}
});
primaryStage.setTitle("FXML-Beispiel");
primaryStage.setScene(new Scene(root, 100, 57));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Die Oberfläche wird hier vollständig in start()
implementiert. Dieses Verfahren ist natürlich der Einfachheit
der Oberfläche und dem Demonstrationsziel geschuldet.
Button
und Label
werden untereinander auf ein GridPane
gesetzt, das schließlich einem scene
-Objekt
übergeben wird. Zur weiteren Demonstration werden den
Komponenten zusätzlich einige Eigenschaften hinzugefügt
und die Schaltfläche erhält in einer anonymen
Klasse seine Funktionalität. Durch einen Klick wird aus
einer Model-Klasse über einen Getter eine Instanzvariable
ausgelesen und deren Wert auf das Label gesetzt.
Stellt man sich
hier eine deutlich komplexeres GUI vor, so wird die
Unübersichtlichkeit deutlich: Der scene graph, die
Hierarchie der Komponenten, ist nur schwer und durch intensive
Einarbeitung in den Text nachzuvollziehen.
Die FXML-Variante
Um den Klassenquelltext für die Verwendung einer *.fxml
-Datei
vorzubereiten, ist nur ein Eingriff nötig: Die gesamte Routine
zur Erzeugung der Oberfläche wird einschließlich der
Importanweisungen gelöscht und durch eine Anweisung ersetzt,
mit der die erwähnte Datei geladen wird.
Um eine
größere Flexibilität zu haben und das hier
verwendete GridPane
beliebig auswechseln zu
können, wird hier eine Variable vom Typ Parent
verwendet. Die Klasse stellt den Supertyp aller Knoten dar, die
Kinder in der graphischen Hierarchie haben können.
Das
Laden selbst geschieht durch die statische Methode load()
eines FXMLLoaders. Hier muss der relative Pfad zur *.fxml
-Datei
angegeben werden.
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class FXMLBsp extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("FXMLBsp.fxml"));
primaryStage.setTitle("FXML-Beispiel");
primaryStage.setScene(new Scene(root, 100, 57));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Die Struktur einer *.fxml
-Datei entspricht mit einigen
Ausnahmen der gängigen Syntax einer xml
-Datei.
Die genaue, ausführliche Dokumentation ist unter https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html
zu finden.
Wie im Java-Quelltext auch, so können auch in *.fxml
-Dateien
Klassennamen entweder voll qualifiziert, mit Package-Angabe, oder
aber per Import-Anweisung angegeben werden.
Im vorliegenden
Beispiel werden die aus dem Klassenquelltext gelöschten
Import-Anweisungen der verwendeten Komponenten mit anderer Syntax
zu Beginn der Datei eingefügt. Sie müssen innerhalb von <?import
... ?>
-Elementen formuliert werden.
Wie in
xml-Dateien kann ein Element entweder aus Start- und End-Tag
bestehen oder, bei fehlendem End-Tag, einen schließenden
Schrägstrich '/' besitzen.
Einen Unterschied gibt es bei
der Groß-/Kleinschreibung: Mit großem Anfangsbuchstaben
geschriebene Elemente werden als Objektdeklarationen behandelt, die
der FXMLLoader zum Anlass nimmt, entsprechende Objektbildungen
vorzunehmen. Kleine Anfangsbuchstaben definieren Eigenschaften, die
wahlweise in Attribut-Form oder als Kindelemente geschrieben werden
können.
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>
<GridPane xmlns:fx="https://javafx.com/fxml/1" xmlns="https://javafx.com/javafx/2.2"
fx:controller="frameworks.javafx.fxml.FXMLBspController">
<children>
<Button fx:id="button" onAction="#manageButton" prefWidth="100.0"
text="Klick!" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label fx:id="label" alignment="CENTER" prefWidth="100.0" text=""
GridPane.columnIndex="0" GridPane.rowIndex="1" />
</children>
</GridPane>
Die Klassenangabe eines importierten, von Node
abgeleiteten Typs, hier GridPane
, bildet das
Wurzelelement. Als Argument werden diesem die Namensraumangaben
für JavaFX und FXML, sowie ein Controller-Name (incl.
Packageangabe) als Wert des Attributs fx:controller
übergeben. Innerhalb des Elementes children
werden dann die Elemente spezifiziert, die durch das GridPane
angeordnet werden sollen.
Sie erhalten Attribute, deren
Bezeichner von den Setter-Methoden der Klasse (ohne 'set),
abgeleitet sind und zusätzlich ein Attribut fx:id
,
das eine ID definiert, mit der das erzeugte Objekt im Quelltext
angesprochen wird.
Der Controller
Über das Attribut onAction
wird im Button-Element
ein Methodenname mit vorangestelltem Doppelkreuz (#
)
angegeben, über den im o.a. Controller die Ereignisbehandlung
erfolgt.
Der Controller ist eine gängige Java-Klasse mit
beliebiger Bezeichnung, die oftmals das Interface Initializable
implementiert, dies jedoch nicht zwingend muss. Das Interface
stellt die Methode initialize()
bereit, die beim
Aufruf des Controllers einmal aufgerufen wird. Im vorliegende Fall wird
ein Objekt einer einfachen Model-Klasse erzeugt, die den Text
bereitstellt, der auf dem Label dargestellt wird.
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class FXMLBspController implements Initializable {
@FXML
private Label label;
private FXMLBspModel model;
@Override
public void initialize(URL location, ResourceBundle resources) {
model = new FXMLBspModel();
}
public void manageButton() {
label.setText(model.getHello());
}
}
Zu Erwähnen ist noch die Deklaration des Labels. Sie ist mit
der annotation @FXML
versehen. Dies bewirkt, dass das
private Objekt wie üblich zwar nicht von anderen Klassen,
jedoch von der Markupsprache aus angesprochen werden kann.
Das Model
public class FXMLBspModel {
private String hello = "Hello World!";
public String getHello() {
return hello;
}
}
1) Im Gegensatz zu den meisten bislang üblichen graphischen Editoren für Swing-Oberflächen, kann und darf eine mit dem Scene Editor erstellte *.fxml-Datei problemlos auch auf andere Art und Weise nachbearbeitet werden.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.