Wie kann in JavaFX eine Tabelle realisiert werden?

TableView ist die in JavaFX gebräuchliche Komponente zur Darstellung von tabellarischen Daten. Die Daten werden hierbei in Spalten organisiert und deren Darstellung durch jene auch verwaltet.

Tabellenspalten

Tabellenspalten sind die primären Organisationseinheiten, die in JavaFX-TableView-Komponenten für die Darstellung von Daten verantwortlich sind. Im Einzelnen sind dies:

Einspaltige JavaFX TableView

Tabellenspalten werden durch die Klasse javafx.scene.control.TableColumn<S,T> repräsentiert. S gibt hierbei den generischen Typ der Tabelle an, T den Inhalt der Zellen der Spalte.

Eine einspaltige Tabelle mit Strings

Das Beispiel zeigt eine einspaltige Tabelle, die ausschließlich mit String-Einträgen gefüllt ist.
In der start()-Methode der Application wird zunächst ein Objekt vom Typ TableView<String> erzeugt. <String> gibt hierbei den Inhaltstyp der Tabelle an. Die dann folgende Definition einer Tabellenspalte, TableColumn<String, String> umfasst zusätzlich zum Inhaltstyp der Tabelle als zweiten noch den konkreten Typ der Spalte. Dem Konstruktor wird zusätzlich noch der String des Spaltentitels als Parameter übergeben.

Bei der neu erzeugten Spalte muss nun die Eigenschaft cellValueFactory gesetzt werden. Sie stellt ein Callback dar, das für das Erneuern des Zelleninhaltes bei Werteänderungen verantwortlich ist. Die hierzu notwendige Überwachung geschieht durch ein Objekt vom Typ ObservableValue, das wiederum durch ein TableColumn.CellDataFeatures zurückgegeben wird.
Callback ist ein funktionales Interface, sodass das Setzen der cellValueFactory ab Java 8 auch in Form eines Lambda-Ausdrucks erfolgen kann.

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;

public class StringTabelleEinspaltig extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("Tabellenbeispiel");
        TableView<String> tView = new TableView<String>();
        TableColumn<String, String> junk = new TableColumn<String, String>(
                "Junkfood");

        junk.setCellValueFactory(
                new Callback<CellDataFeatures<String, String>, ObservableValue<String>>() {
                    public ObservableValue<String> call(
                            CellDataFeatures<String, String> p) {
                        return new SimpleStringProperty(p.getValue());
                    }
                });

        // alternativ Lambda-Expr.
        // junk.setCellValueFactory((p) -> {
        // return new SimpleStringProperty(p.getValue());
        // });

        tView.getColumns().add(junk);
        tView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        tView.setItems(createList());
        Scene scene = new Scene(tView, 250, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private ObservableList<String> createList() {
        return FXCollections.observableArrayList("Schaschlik", "Currywurst",
                "Frikadelle", "Pommes", "Hummer an Salbei-Zitonen-Schaum");
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Nach Fertigstellung der Spalte wird diese der Liste der zur Tabelle gehörigen Tabellenspalten hinzugefügt. Die Liste wird als ObservableList durch die Methode TableView#getColumns() ermittelt.
Um die Spalte auf Tabellenbreite zu expandieren wird dann die columnResizePolicy mit dem Feld CONSTRAINED_RESIZE_POLICY belegt.

Das Füllen der Spalte mit Inhalt geschieht zum Abschluss durch eine ObservableList, die durch die Methode createList() zurückgegeben wird. Die Liste wird durch eine statische Factory-Methode der Klasse FXCollections geliefert, der die einzelnen Strings übergeben werden. Sie wird durch setItems() direkt auf die Tabelle gesetzt.

Eine mehrspaltige Tabelle mit Einträgen unterschiedlicher Datentypen

Das folgende Beispiel demonstriert die Anlage einer TableView-Tabelle, deren Inhalte aus den Eigenschaften eines selbst definierten Datentyps bestehen.

Einspaltige JavaFX TableView

Die Klasse Moped

Die Klasse Moped erfasst diverse einfache Daten historischer Motorräder, deren Eigenschaften in vier Instanzvariablen, teils als String, teils als Integer erfasst werden.
Hierbei fällt auf, dass diese nicht, wie gewohnt im Falle int, die primitiven Datentypen oder deren Wrapper selbst speichern, sondern Property-Objekte. Sie kapseln die Daten und sind imstande, bei Werte-Änderungen auf diese in verschiedenen Zusammenhängen, z.B. hier in einer Tabellendarstellung, zu reagieren. Die Initialisierung der Eigenschaften erfolgt im Konstruktor bei der Objektbildung.
Um einen kontrollierten Zugriff sowohl auf die Daten selbst, als auch auf die Properties zu ermöglichen, werden zusätzliche Accessormethoden bereitgestellt.

Eine mehrspaltige Tabelle

Die Klasse erweitert Application, überschreibt deshalb start(), enthält main() und stellt die eigentliche Tabelle in Form einer TableView bereit. Sie wird direkt bei der Instanzierung erzeugt und in start() weiter konfiguriert. Dabei ist das Einfügen von Tabellenspalten als zentral anzusehen.

Das Spalten-Objekt TableColumn ist, wie oben bereits angesprochen, ein parametrisierter Typ, dem die Klasse des Tabelleninhalts (hier Moped) und der Typ des Spalteninhalts (hier teils String, teils Integer) bekanntgegeben werden muss. Zusätzlich wird der String des Tabellentitels dem Konstruktor jeder Spalte übergeben. Für jede Spalte wird dann der Wert der Eigenschaft cellValueFactory gesetzt. Dies geschieht hier durch Lambda-Ausdrücke. Sie nutzen die Getter-Methoden der Klasse Moped, um die jeweiligen Eigenschaften der erzeugten Objekte auszulesen. Es fällt hier auf, dass jede Property durch eine Methode asObject() weiter verarbeitet wird. Diese liefert eine ObjectProperty, die in beide Richtungen an die Property gebunden ist, auf der sie aufgerufen wird. Sie dient dazu, Änderungen an den zugehörigen Daten weiterzureichen und so für eine automatische Aktualisierung zu sorgen.

Auch die Breite der Tabellenspalte wird durch eine Property gesteuert. Sie wird an die Breite der Tabelle gebunden. Dies geschieht so, dass über die Methode bind() ein prozentualer Breitenanteil der gesamten Tabelle an die Eigenschaft der bevorzugten Breite der Spalte gebunden wird. Die Methode multiply() errechnet diese Breite mit Hilfe eines übergebenen dezimalen double-Wertes.

Nach der Einrichtung der Spalten werden diese der TableView hinzu- und der Inhalt der Tabelle eingefügt. Dieser wird in Form einer ObservableList in der Methode createList() erzeugt und durch diese zurückgegeben.

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class TabelleMehrspaltig extends Application {

    private TableView<Moped> tView = new TableView<Moped>();

    @SuppressWarnings("unchecked")
    @Override
    public void start(Stage stage) {
        stage.setTitle("Modeds");
        stage.setWidth(350);
        stage.setHeight(300);

        tView.setEditable(true);

        TableColumn<Moped, String> nameCol = new TableColumn<Moped, String>(
                "Name");
        nameCol.setCellValueFactory((p) -> {
            return p.getValue().nameProperty();
        });
        nameCol.prefWidthProperty().bind(tView.widthProperty().multiply(0.45));

        TableColumn<Moped, Integer> bjCol = new TableColumn<Moped, Integer>(
                "Baujahr");
        bjCol.setCellValueFactory((p) -> {
            return p.getValue().baujahrProperty().asObject();
        });
        bjCol.prefWidthProperty().bind(tView.widthProperty().multiply(0.2));
        
        TableColumn<Moped, Integer> psCol = new TableColumn<Moped, Integer>(
                "PS");
        psCol.setCellValueFactory((p) -> {
            return p.getValue().psProperty().asObject();
        });
        psCol.prefWidthProperty().bind(tView.widthProperty().multiply(0.1));
        
        TableColumn<Moped, Integer> ccmCol = new TableColumn<Moped, Integer>(
                "Hubraum");
        ccmCol.setCellValueFactory((p) -> {
            return p.getValue().hubraumProperty().asObject();
        });
        ccmCol.prefWidthProperty().bind(tView.widthProperty().multiply(0.25));
        
        tView.getColumns().addAll(nameCol, bjCol, psCol, ccmCol);

        tView.setItems(createList());
        Scene scene = new Scene(tView);
        stage.setScene(scene);
        stage.show();
    }

    private ObservableList<Moped> createList() {
        final ObservableList<Moped> mopeds = FXCollections.observableArrayList(
                new Moped("Norton Manx", 1954, 53, 500),
                new Moped("Moto Guzzi V8", 1957, 72, 500),
                new Moped("BMW RS", 1953, 60, 500),
                new Moped("Megola", 1924, 14, 640),
                new Moped("B\u00F6hmerland", 1939, 24, 600));
        return mopeds;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

public class Moped {
    private StringProperty name;
    private IntegerProperty baujahr, ps, hubraum;

    public Moped(String name, int baujahr, int ps, int hubraum) {
        this.name = new SimpleStringProperty(name);
        this.baujahr = new SimpleIntegerProperty(baujahr);
        this.ps = new SimpleIntegerProperty(ps);
        this.hubraum = new SimpleIntegerProperty(hubraum);
    }

    public int getHubraum() {
        return hubraum.get();
    }
    
    public IntegerProperty hubraumProperty() {
        if (hubraum == null) hubraum = new SimpleIntegerProperty(-1);
        return hubraum;
    }

    public void setHubraum(int hubraum) {
        this.hubraum.set(hubraum);
    }
    
    public int getPs() {
        return ps.get();
    }

    public IntegerProperty psProperty() {
        if (ps == null) ps = new SimpleIntegerProperty(-1);
        return ps;
    }

    public void setPs(int ps) {
        this.ps.set(ps);
    }
    
    public int getBaujahr() {
        return baujahr.get();
    }

    public IntegerProperty baujahrProperty() {
        if (baujahr == null) baujahr = new SimpleIntegerProperty(-1);
        return baujahr;
    }

    public void setBaujahr(int baujahr) {
        this.baujahr.set(baujahr);
    }
    
    public String getName() {
        return name.get();
    }

    public StringProperty nameProperty() {
        if (name == null) name = new SimpleStringProperty("-");
        return name;
    }

    public void setName(String name) {
        this.name.set(name);
    }
}

Quellen

https://docs.oracle.com/javafx/2/ui_controls/table-view.htm

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.