Wie kann in JavaFX eine Tabelle realisiert werden?
Tabellenspalten
Tabellenspalten sind die primären Organisationseinheiten, die in JavaFX-TableView-Komponenten für die Darstellung von Daten verantwortlich sind. Im Einzelnen sind dies:
- Bereitstellung einer Spaltenüberschrift
- Verwaltung von eingebetteten Spalten
- An- und Ausschalten der Sichbarkeit der Spalte
- Anzeige eines Kontextmenus
- Sortierung des Spalteninhaltes
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.
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
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.