Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Hallo, ich suche ein geeignetes Fenster zum anzeigen von Wetter Daten.
Ich habe mir mit javafx eine Anzeigeoberfläche gebastelt, wo man mehrere Auswahl hat, wie Wetter letzten 24h, letzte Woche, ganzer Monat etc.
Wenn ich auf ein Button drücke soll sich ein weiteres Fenster öffnen und dies dann anzeigen.
Jetzt weiß ich nicht so recht was ich nehmen soll und dafür geeignet ist. Und da dachte ich frag mal euch Experten.
Ich würde das Modul Tableview nehmen. Wäre das ratsam?
Da wir nicht wissen, was Du darstellen willst, ist das etwas, das wir Dir nicht sagen können.
Die TableView ist gut, wenn Du eine Tabelle anzeigen willst. Also wenn Du bei den letzten 24 Stunden das als Tabelle machst mit 24 Zeilen (pro Stunde eine Zeile) und dann Spalten für diverse Informationen wie Temperatur, Niederschlagswahrscheinlichkeit, ....
Ich hab mich jetzt für TableView entschieden.
Ja ich möchte, wenn ich z.B. Wetterdaten von einen Monat angezeigt bekomme, alles übersichtlich haben.
Ich habe jetzt mal eine Klasse erstellt, die von Stage erbt, damit ich sie auch vom Fenster aufrufen kann. Getestet mit einfachen Label habe ich das, und hat funktioniert.
Jetzt habe ich versucht das Ganze mit TableView zu machen, aber da wird mir beim Einfügen der Zeilen bei der Methode "observableArrayList" einen Fehler angezeigt. Jetzt frage ich mich wo es da hapert:
Java:
public class Anzeigefenster extends Stage{
Stage meinStage;
TableView<String> table = new TableView<>();
private final ObservableList<Person> data = FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson")
);
public Anzeigefenster(String x) throws IOException {
String[] getliste = {"hallo"};
meinStage = this;
TableColumn firstNameCol = new TableColumn("First Name");
TableColumn lastNameCol = new TableColumn("Last Name");
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
Scene scene = new Scene(new Group());
((Group) scene.getRoot()).getChildren().addAll(vbox);
meinStage.setTitle("Wetter");
meinStage.setWidth(800);
meinStage.setHeight(500);
meinStage.setScene(scene);
meinStage.show();
}
}
Und die Klasse Person:
Java:
public class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private Person(String fName, String lName) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
}
Vielleicht weiß ja jemand wo der Fehler liegt.
Ach ja, das ist alles nur ein Beispiel, die namen etc. sind erstmal so übernommen, natürlich wenn der Code Gerüst steht werde ich noch verfeinern.
Fügt man bei FXCollections.observableArrayList() nicht Elemente mit add hinzu ?
zb so.
final ObservableList<Person> dummyData = FXCollections.observableArrayList();
for (int i = 0; i < numberOfEntries; i++) {
dummyData.add(createNewRandomPerson());
}
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml@21.0.2/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1858)
at javafx.fxml@21.0.2/javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1726)
at javafx.base@21.0.2/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at javafx.base@21.0.2/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:232)
at javafx.base@21.0.2/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:189)
at javafx.base@21.0.2/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at javafx.base@21.0.2/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at javafx.base@21.0.2/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@21.0.2/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@21.0.2/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@21.0.2/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@21.0.2/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@21.0.2/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at javafx.base@21.0.2/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.base@21.0.2/javafx.event.Event.fireEvent(Event.java:198)
at javafx.graphics@21.0.2/javafx.scene.Node.fireEvent(Node.java:8875)
at javafx.controls@21.0.2/javafx.scene.control.Button.fire(Button.java:203)
at javafx.controls@21.0.2/com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:207)
at javafx.controls@21.0.2/com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
at javafx.base@21.0.2/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)
at javafx.base@21.0.2/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at javafx.base@21.0.2/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:232)
at javafx.base@21.0.2/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:189)
at javafx.base@21.0.2/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at javafx.base@21.0.2/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at javafx.base@21.0.2/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@21.0.2/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@21.0.2/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@21.0.2/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base@21.0.2/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base@21.0.2/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at javafx.base@21.0.2/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.base@21.0.2/javafx.event.Event.fireEvent(Event.java:198)
at javafx.graphics@21.0.2/javafx.scene.Scene$MouseHandler.process(Scene.java:3984)
at javafx.graphics@21.0.2/javafx.scene.Scene.processMouseEvent(Scene.java:1890)
at javafx.graphics@21.0.2/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2708)
at javafx.graphics@21.0.2/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:411)
at javafx.graphics@21.0.2/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:301)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics@21.0.2/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:450)
at javafx.graphics@21.0.2/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
at javafx.graphics@21.0.2/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:449)
at javafx.graphics@21.0.2/com.sun.glass.ui.View.handleMouseEvent(View.java:551)
at javafx.graphics@21.0.2/com.sun.glass.ui.View.notifyMouse(View.java:937)
at javafx.graphics@21.0.2/com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at javafx.graphics@21.0.2/com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$10(GtkApplication.java:263)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:119)
at java.base/java.lang.reflect.Method.invoke(Method.java:577)
at com.sun.javafx.reflect.Trampoline.invoke(MethodUtil.java:72)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:577)
at javafx.base@21.0.2/com.sun.javafx.reflect.MethodUtil.invoke(MethodUtil.java:270)
at javafx.fxml@21.0.2/com.sun.javafx.fxml.MethodHelper.invoke(MethodHelper.java:84)
at javafx.fxml@21.0.2/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1853)
... 46 more
Caused by: java.lang.Error: Unresolved compilation problems:
The method observableArrayList(Callback<E,Observable[]>) in the type FXCollections is not applicable for the arguments (Person, Person)
The constructor Person(String, String) is not visible
The constructor Person(String, String) is not visible
at datenbank_1.Anzeigefenster.<init>(Anzeigefenster.java:30)
at datenbank_1.Eventhandling.twentyFourHours(Eventhandling.java:22)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
... 53 more
Ich habe es geändert, habe ich wohl übersehen. Jetzt funktioniert es.
Hier nochmal der Code:
Java:
public class Anzeigefenster extends Stage{
Stage meinStage;
public Anzeigefenster(String x) throws IOException {
meinStage = this;
TableView<Person> table = new TableView<Person>();
final ObservableList<Person> data = FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson")
);
TableColumn firstNameCol = new TableColumn("First Name");
TableColumn lastNameCol = new TableColumn("Last Name");
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
Scene scene = new Scene(new Group());
((Group) scene.getRoot()).getChildren().addAll(vbox);
meinStage.setTitle("Wetter");
meinStage.setWidth(800);
meinStage.setHeight(500);
meinStage.setScene(scene);
meinStage.show();
}
}
U.a. bei Zeile 14 und 15 wird mir der Hinweis
Code:
- TableColumn is a raw type. References to generic type TableColumn<S,T> should be parameterized
public class Person {
private StringProperty firstName;
public void setFirstName(String value) {
firstNameProperty().set(value);
}
public String getFirstName() {
return firstNameProperty().get();
}
public StringProperty firstNameProperty() {
if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
return firstName;
}
.
.
.
Wie muss ich das verstehen? Damit meine ich
"firstNameProperty().set(value)" und " firstNameProperty().get()"?
Kann ich im folgenden Code auch statt die 2 Strings auch eine Methode angeben, die über mehrere Strings iteriert und und eine Art Zweidimensionales Array oder so zurück geben?
Java:
final ObservableList<Person> data = FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
Ich meinte damit, das man bei "new Person(String, String)" statt den Strings auch eine Methode angeben kann, die eine Liste von Strings zurück gibt.
Beispiel:
Java:
public String[][] namen(){
String[] vorNamen = {"Kurt", "Olaf", "Markus"};
String[] nachNamen = {"Meier","Schulz", "Schmitz"};
return (vorNamen,nachNamen);
}
final ObservableList<Person> data = FXCollections.observableArrayList(
new Person(namen()),
mir geht es darum, das ich in einer Methode die ganzen Wetterdaten "sammle" (in Arrays z.B.) und dann mit einmal an
Java:
final ObservableList<Person> data = FXCollections.observableArrayList(...)
weiter gebe.
Was ich mich frage, was machen eigentlich die ganzen setter Methoden in Person? Man ruft doch die Klasse ohnehin mit den Konstruktor auf und übergibt somit die Daten.
Jein, der new-Operator erzeugt ein einziges Objekt. Im Fall einer Klasse Person ist das üblicherweise eben eine einzige Person.
Natürlich kannst Du eine Klasse (z. B. Personen oder PersonenListe) erstellen, die eine Liste von Personen verwaltet. Diese Klasse könnte dann auch einen Konstruktor anbieten, der eine Liste von Namen verarbeitet. Du kannst aber auch eine Factory-Methode erstellen, die eine Liste von Personen zurückgibt.
Was ich mich frage, was machen eigentlich die ganzen setter Methoden in Person? Man ruft doch die Klasse ohnehin mit den Konstruktor auf und übergibt somit die Daten.
Das hängt mit der JavaBeans-Konvention für Properties zusammen.
Wenn das Objekt eine veränderliche Property "firstName" hat, dann werden Getter und Setter erwartet. Die Methodennamen müssen mit get/set beginnen und daran wird jeweils der PropertyName in UpperCamelCase angehängt. Für eine Property "firstName" gibt es dann z. B. getFirstName und setFirstName.:
Java:
class Person {
private String firstName;
public String getFirstName() { return firstName; }
public void setFirstName(String name) { firstName = name; }
}
In JavaFX werden Properties über Property-Objekte realisiert, die Konvention hier ist, dass es eine Methode gibt, die mit der Propertynamen in lowerCamelCase beginnt und daran Property angeschlossen wird und eine JavaFX-Property-Typ zurückgibt.
Java:
class Person {
private StringProperty firstName = new SimpleStringProperty();
public StringProperty firstNameProperty() { return firstName; }
}
Jetzt kann man beide Konventionen vereinen:
Java:
class Person {
private StringProperty firstName = new SimpleStringProperty();
public String getFirstName() { return firstName.getValue(); }
public void setFirstName(String name) { firstName.setValue(name); }
public StringProperty firstNameProperty() { return firstName; }
}
Ich bin höchstwahrscheinlich auf dem Holzweg, weil ich mich noch nicht so mit den StringProperty , FXCollections und und und auskenne habe ich mal folgendes versucht.
Java:
public class Anzeigefenster extends Stage{
Stage meinStage;
MeineDaten met = new MeineDaten();
public Anzeigefenster(String x) throws IOException {
meinStage = this;
ObservableList<Person> data = FXCollections.observableArrayList(new Person("null"));
TableView<Person> table = new TableView<Person>();
for (int i=0; i < met.daten().length; i++) {
data = FXCollections.observableArrayList(new Person(met.daten()[i]));
}
TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
table.getColumns().add(firstNameCol);
table.getColumns().add(lastNameCol);
table.setItems(data);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
Scene scene = new Scene(new Group());
((Group) scene.getRoot()).getChildren().addAll(vbox);
meinStage.setTitle("Wetter");
meinStage.setWidth(800);
meinStage.setHeight(500);
meinStage.setScene(scene);
meinStage.show();
}
}
class MeineDaten{
protected String[] daten() {
String[] x = {"montag", "dienstag", "mittwoch"};
return x;
}
}
Aber das gibt auch ne Fehlermeldung, aber so etwas habe ich mir vorgestellt.
Ich habe jetzt mal noch eine Klasse namens DatabaseModel und brauch nochmal etwas Hilfe.
Wenn ich das ganze starte gibt der Compiler eine Fehlermeldung ( Cannot invoke "javafx.scene.control.TableColumn.setCellValueFactory(javafx.util.Callback)" because "this.vorname" is null) aus, das heißt es wird null zurück gegeben. Wo könnte hier der Fehler liegen?
Java:
public class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty tele;
Person(String fName, String lName, String tele) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.tele = new SimpleStringProperty(tele);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String gettele() {
return tele.get();
}
public void setTele(String fName) {
tele.set(fName);
}
}
Java:
public class DatabaseModel {
String[][] liste = {{"Matze", "Meier", "354875n"}, {"Egon", "Schulz", "485734"}, {"Kurt", "Schmitz", "4567654"}};
private ObservableList<Person> data;
public DatabaseModel() {
data = FXCollections.observableArrayList();
for (int i = 0; i < liste.length; i++) {
data.add(new Person(liste[i][0],liste[i][1],liste[i][1]));
}
}
public ObservableList<Person> getPerson(){
return data;
}
public void setPerson(ObservableList<Person> data){
this.data = data;
}
}
Java:
public class Anzeigefenster extends Stage{
Stage meinStage;
private DatabaseModel data = new DatabaseModel();
private TableView<Person> table;
private TableColumn<Person, String> vorname;
private TableColumn<Person, String> nachname;
private TableColumn<Person, String> telefon;
public Anzeigefenster(String x) throws IOException {
meinStage = this;
vorname.setCellValueFactory(new PropertyValueFactory<Person, String>("Vorname"));
nachname.setCellValueFactory(new PropertyValueFactory<Person, String>("Nachname"));
telefon.setCellValueFactory(new PropertyValueFactory<Person, String>("telefon"));
table.setItems(data.getPerson());
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
Scene scene = new Scene(new Group());
((Group) scene.getRoot()).getChildren().addAll(vbox);
meinStage.setTitle("Wetter");
meinStage.setWidth(800);
meinStage.setHeight(500);
meinStage.setScene(scene);
meinStage.show();
}
}
Wenn ich das ganze starte gibt der Compiler eine Fehlermeldung ( Cannot invoke "javafx.scene.control.TableColumn.setCellValueFactory(javafx.util.Callback)" because "this.vorname" is null) aus,
Du hast in der Klasse Anzeigefenster die Felder table, vorname, nachname und telefon. Keines dieser Felder initialisierst Du, daher sind alle null.
Daher scheitern alle Aufrufe, die Du auf den Felder machst. Da der erste Aufruf vorname.setCellValueFactory ist, bekommst Du da die Fehlermeldung.
Du musst also auch eine TableView erzeugen sowie die TableColumn.
Und auch das meinStage = this kannst Du weglassen. Du brauchst doch die Variable / das Feld meinStage nicht, da Du überall einfach this verwenden kannst.
public class Anzeigefenster extends Stage{
Stage meinStage;
private DatabaseModel data = new DatabaseModel();
public Anzeigefenster(String x) throws IOException {
meinStage = this;
TableView<Person> table = new TableView<Person>();
TableColumn<Person, String> vorname = new TableColumn<Person, String>("firstName");
TableColumn<Person, String> nachname = new TableColumn<Person, String>("lastName");
TableColumn<Person, String> telefon = new TableColumn<Person, String>("tele");
vorname.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
nachname.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
telefon.setCellValueFactory(new PropertyValueFactory<Person, String>("tele"));
table.setItems(data.getPerson());
table.getColumns().add(vorname);
table.getColumns().add(nachname);
table.getColumns().add(telefon);
table.setItems(data.getPerson());
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
Scene scene = new Scene(new Group());
((Group) scene.getRoot()).getChildren().addAll(vbox);
meinStage.setTitle("Wetter");
meinStage.setWidth(800);
meinStage.setHeight(500);
meinStage.setScene(scene);
meinStage.show();
}
}
Was ich aber nicht verstehe ist, wenn ich in Zeile 10 und 14 "firstName" in "vorname" umschreibe kommt eine Fehlermeldung. Warum das?
Und ohne "meinStage = this;" kommt auch ein Fehler.
Du musst auch den Getter und Setter anpassen. (Damit es funktioniert, reicht der Getter schon aus. PropertyValueFactory nutzt eine PropertyReference und die schaut nach einer Methode get + dem übergebenen Wert mit erstem Buchstaben groß. Also in Deinem Fall nach der Methode getVorname().
Das war es , jetzt funktioniert es so wie ich es mir vorgestellt habe.
Jetzt muss ich nur noch "TableColumn" in ein Klasse auslagern und als Liste zurück geben, sofern es geht.
Java:
//dient zur Anzeige in der Tabelle
TableColumn<Person, String> vorname = new TableColumn<Person, String>("vornameeee");
//greift auf Person zu und ordnet vornameeee der Spalte vorname zu (richtig?)
vorname.setCellValueFactory(new PropertyValueFactory<Person, String>("vorname"));
Jetzt baue ich eine neue Klasse die im Prinzip "new TableColumn<Person, String>("xxx");" und ".setCellValueFactory(new PropertyValueFactory<Person, String>("vorname"));" als Liste zurück gibt.
Geht da so überhaupt?
Ziel ist in der GUI Klasse keine "statischen" Daten zu haben.
Ich frage mich gerade ob das überhaupt funktioniert das ich es genau so auf ne andere Klasse auslagere wie beim "DatabaseModel" (siehe #20).
Weil jeder Spaltenname brauch eine eigene Instanz der Klasse "TableColumn".
Also müsste ich in meiner Verarbeitungsklasse für jede Spalte ein neues Objekt der Klasse "TableColumn" erzeugen.
Ziel ist des ganzen ist, das ich die Oberflächenanzeige so abstrakt wie möglich halten möchte, und die Unterklassen so spezifieren das, wenn ich es mal erweitern möchte, die Oberflächenklassen nicht mehr "anfassen" muss.
Ich denke mal so macht man das auch und habe es so gelernt bzw immer gelesen.
Also hier weiss ich gerade nicht, was genau Dein Ziel ist. Generell wäre aus meiner Sicht wichtiger, eine deutliche Trennung der Aufgaben vorzunehmen. Bei so Desktop Anwendungen ist MVVM das Pattern, das ich empfehlen würde. Dazu gibt es bei Java z.B. MVVMfx. Damit liesse sich dann relativ gut in Model - View - ViewModel aufteilen.
Model ist dann eine Daten Klasse ohne irgendwelche UI spezifischen Dinge. Die ganzen *Property Klassen sind JavaFX spezifisch und haben daher in einer Model Klasse nichts verloren. Statt (Simple)StringProperty sollte im Model schlicht String verwendet werden. Die *Property Klassen kommen dann im ViewModel in Spiel. Siehe dazu z.B. einfach mal ModelWrapper · sialcasa/mvvmFX Wiki (github.com)
Und die UI wird dann meist auch deklarativ beschrieben. Also statt so Code zu schreiben hast Du dann einfach ein fxml File und das wird dann geladen. Der Controller aus dem fxml File ist meiner Meinung nach dann mit Bestandteil der View. (Daher heissen bei mir die Dateien dann xxxxView.fxml und xxxxView.java)
In der Regel ist das also eher eine Richtung in die gegangen wird. Aber das heisst natürlich nicht, dass Deine Idee falsch ist. (Ich habe die ja nicht einmal verstanden, daher kann ich es nicht bewerten!). Vielleicht hast Du ja einen anderen Weg vor Augen, der auch Sinn machen kann.
OK, also wie ich es verstehe, erstelle ich eine Klasse (MeineDaten), die mir die ganzen Daten z.B. von einer Datenbank holt.
Die klasse hat dann nichts mit UI zu tun.
Dann erstelle ich eine Klasse (DatenView) die sich die Daten von der Klasse "MeineDaten" holt und zu "StringProperty" umwandelt.
Diese Daten packe ich in ein "FXCollections.observableArrayList()" und gebe das ganze dann an meine eigentliche Klasse die die Oberfläche erstellt (MeineAnzeige).
Habe ich das soweit verstanden?
Was ich noch nicht so recht weiß wie ich das dann mit "TableColumn" mache. Da möchte ich die Spaltennamen nicht direkt in die Klasse MeineAnzeige erstellen, sondern auch in einer Klasse wo nur Daten verarbeitet werden.
Das Model hat in der Regel zwei Arten von Klassen: DTO (Data Transfer Objects, Entitites, POJO, ...) und DAO (Data Access Objects, hier werden die Daten z.B. aus einer Datenbank geladen und die DTO erzeugt)
Was ich noch nicht so recht weiß wie ich das dann mit "TableColumn" mache. Da möchte ich die Spaltennamen nicht direkt in die Klasse MeineAnzeige erstellen, sondern auch in einer Klasse wo nur Daten verarbeitet werden.
Das Kernproblem hier ist, dass Du bei der Entwicklung gerne Dinge aufteilst. Dieses "Separation of Concerns". Du hast hier halt unterschiedliche Ebenen:
In den Daten hast Du einfach nur einfache Daten. Da hast Du keinerlei Informationen:
Wie Daten formatiert werden
Wie Daten bezeichnet werden (In der Regel entwickelt man auf Englisch, aber die Oberflächen sind dann in Deutsch, Englisch, Französisch, ....)
Was überhaupt angezeigt wird. Eine ID wird ggf. nicht angezeigt. Je nach Ansicht, werden Daten weggelassen (Eine Liste von Kunden wird nicht die USt. Id der Kunden auflisten. Auch die genaue Anschrift ist evtl. nicht notwendig. In einer Liste passen evtl. nur Kundenname, Stadt und Ansprechpartner oder so ...
Daher hat man hier oft eine komplette Trennung und die UI ist in der Regel eben nicht in den Daten mit beschrieben.
Aber natürlich: Dieses Anliegen ist nicht neu. So kann man zusätzliche Informationen mit in der Datenklasse unterbringen:
Evtl. eine Annotation für eine Formatangabe
Evtl. hast Du die Bezeichnung in einer Ressource Datei und Dann hast Du da Schlüssel wie Klasse_fieldName oder so.
Evtl. hast Du Annotations, mit denen Du angeben kannst, wann was angezeigt werden soll ...
Es gibt hier viele Möglichkeiten und Ideen. Unter dem Strich läuft das aber dann darauf hinaus, dass Du diese Informationen per Reflection ausliest und dann Dinge per Code erzeugst. In dem konkreten Fall hast Du ja die Information, dass die Klasse Person angezeigt werden soll. Für die Spalten brauchst Du Getter Methoden, also lässt Du Dir diese einfach per Reflection ausgeben und schon hast Du die Tabelle ganz dynamisch erstellt.
Das nur als kleine Anregung, wohin die Reise gehen könnte.
Das Model hat in der Regel zwei Arten von Klassen: DTO (Data Transfer Objects, Entitites, POJO, ...) und DAO (Data Access Objects, hier werden die Daten z.B. aus einer Datenbank geladen und die DTO erzeugt)
das so wichtig ob DTO oder DAO?
Ist das nur weil man das so "umgangssprachlich macht oder in Java fest verankert?
Aber ich könnte doch eine Klasse Model erstellen die sich die Daten von einer Datenbank holt und mit Getter Methoden weiter gibt, was aber gegen die "regeln" von Java code verstößt?
Denn coden kann man ja alles.
Generell ist die Trennung nicht zwingend notwendig. So sind z.B. Factory Methoden denkbar, die entsprechende Instanzen erzeugen. So eine Factory Methode könnte rein theoretisch auch Daten aus einer Datenbank laden.
Aber problematisch wird es, sobald Du irgendwelche Daten auch zwischen zwei Aufrufen halten möchtest: Du wirst da dann vermutlich Klassenvariablen nutzen und schon bekommst Du einiges an static Variablen und Methoden.
Erfahrungsgemäß ist dies schwerer zu testen.
Du hast hier zwangsläufig auf alle Möglichkeiten der Inheritance zu verzichten.
Du hast natürlich auch hier den Punkt: Separation of Concerns - Du willst also nicht alles in eine Klasse stopfen, wenn es nicht notwendig ist.
Verwaltung der Abhängigkeiten: Du hast Module, die die DTOs kennen müssen (z.B. eine View), aber die haben nichts mit den DAOs zu tun. Eine Trennnung macht hier auch die Verwaltung der Abhängigkeiten einfacher. Die DTOs gibst du z.B. auch nach außen, wenn Du Daten weiter gibst.
Daher wird in der Regel zwischen DTO und DAO getrennt. Aber da gibt es keine Zwänge - das sind nur Dinge, die man als eine Art Best Practice sehen kann - resultierend aus Clean Code Gedanken.
Ich habe mir mal die Problematik angeschaut und durchdacht. Das mit der DTO ist ja klar.
Nur ob mein Vorhaben eine solche braucht weiß ich nicht.
Denn bei einer DTO und auch DTOView, müssen erst Daten gesetzt werden um welche zurück zu geben.
Darin verstehe ich, dass man ja trotzdem, um die "Tableview" zu füllen, in der Gui Daten abfordert.
Mein Gedankengang ist der, das ich eine Vorhandene Datenbank mit Wetterdaten habe (3 Spalten mit Temperatur, Niederschlag und Windstärke).
Diese Daten möchte ich beim Aufruf der "TableView" angezeigt bekommen. Also brauche ich ja einen Zulieferer der Daten.
Das könnte ich mit ner Klasse realisieren die die ganze Datenbank einliest und die Daten für jede spalte separat in ein Array etc. speichert.
Diese Klasse hat noch nichts mit einer GUI zu tun. In einer Klasse z.B. DatenView holt sich das Object die Daten von der Datenklasse und "wandelt" sie in ein "observableArrayList()" oder "SimpleStringProperty()" um und gibt dann die fertigen Daten zurück.
In der GUI Klasse mit "tableView" übergebe ich nur noch die Daten von der Klasse DatenView.
Ich hoffe ich bin nicht auf dem Holzweg. Aber so stelle ich mir das vor.
Deswegen brauche ich in der Daten Klasse keine setter-Methoden.
Ich denke dein Hauptproblem ist das du das observer Prinzip nicht richtig verstanden hast.
Du hast ja eine oserverlist wenn sich in Ihr was ändert wird ja auch deine Tabelle verändert. Und wo veränderst du die liste? Nicht in der Gui View.
Zunächst einmal hast Du Wetterdaten wie Temperatur, Windstärke und Niederschlag, vermutlich auch einen Zeitpunkt. Das kann man in einer entsprechenden Klasse oder mit einem Record darstellen:
Java:
public record WeatherData(LocalDateTime time, double temperature, int bft, double precipitation) {}
Die Wetterdaten möchtest Du in einer Tabelle anzeigen, also brauchst Du ein entsprechendes UI, in JavaFX wäre das wohl die TableView:
Java:
TableView<WeatherData> weatherTable = new TableView<>();
TableColumn<WeatherData, String> timeCol = new TableColumn<>("Zeit");
timeCol.setCellValueFactory(w -> new SimpleStringProperty(w.getValue().time().toString()));
TableColumn<WeatherData, Number> temperatureCol = new TableColumn<>("Temperatur");
temperatureCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().temperature()));
TableColumn<WeatherData, Number> bftCol = new TableColumn<>("bft");
bftCol.setCellValueFactory(w -> new SimpleIntegerProperty(w.getValue().bft()));
TableColumn<WeatherData, Number> precipitationCol = new TableColumn<>("Niederschlag");
precipitationCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().precipitation()));
weatherTable.getColumns().addAll(timeCol, temperatureCol, bftCol, precipitationCol);
Du siehst schon am Typparameter, dass die Tabelle Wetterdaten anzeigen wird. Soweit, so gut.
Wo kommen jetzt die Daten her? Es wird irgendetwas benötigt, das z. B. eine Liste aller Wetterdaten liefert. Das lässt sich einmal ganz abstrakt mit einem Interface darstellen:
Java:
public interface WeatherDataRepository {
List<WeatherData> findAll();
}
Das reicht schon, um die Tabelle mit Daten zu befüllen. Sagen wir mal die Variable repository wäre vom Typ WeatherDataRepository, dann könnten wir im UI schreiben:
Der Code würde - in entsprechenden Methoden - sogar kompilieren. Was noch fehlt ist die Implementierung des Repositories. Hier mal als Klasse:
Java:
class DummyWeatherDataRepository implements WeatherDataRepository {
@Override
public List<WeatherData> findAll() {
return List.of(
new WeatherData(LocalDateTime.parse("2024-02-27T00:00"), 3.2, 2, 0),
new WeatherData(LocalDateTime.parse("2024-02-27T01:00"), 3.4, 1, 1),
new WeatherData(LocalDateTime.parse("2024-02-27T02:00"), 4.1, 3, 3),
new WeatherData(LocalDateTime.parse("2024-02-27T03:00"), 5.0, 2, 0));
}
}
Wo findAll() die Daten herbekommt, spielt für den Rest der Anwendung natürlich keine Rolle. Deine Implementierung kann z. B. auf eine Datenbank zugreifen.
So, und damit haben wir auch schon alles beisammen:
Java:
public class Weather extends Application {
private WeatherDataRepository repository;
@Override
public void init() throws Exception {
repository = new DummyWeatherDataRepository();
}
@Override
public void start(Stage stage) throws Exception {
TableView<WeatherData> weatherTable = createWeatherDataTable();
weatherTable.getItems().addAll(repository.findAll());
Scene scene = new Scene(weatherTable);
stage.setScene(scene);
stage.show();
}
private TableView<WeatherData> createWeatherDataTable() {
TableView<WeatherData> weatherTable = new TableView<>();
TableColumn<WeatherData, String> timeCol = new TableColumn<>("Zeit");
timeCol.setCellValueFactory(w -> new SimpleStringProperty(w.getValue().time().toString()));
TableColumn<WeatherData, Number> temperatureCol = new TableColumn<>("Temperatur");
temperatureCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().temperature()));
TableColumn<WeatherData, Number> bftCol = new TableColumn<>("bft");
bftCol.setCellValueFactory(w -> new SimpleIntegerProperty(w.getValue().bft()));
TableColumn<WeatherData, Number> precipitationCol = new TableColumn<>("Niederschlag");
precipitationCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().precipitation()));
weatherTable.getColumns().addAll(timeCol, temperatureCol, bftCol, precipitationCol);
return weatherTable;
}
public static void main(String[] args) {
Application.launch(args);
}
}
Sehr gute Anleitung.
Aber bevor ich deine gelesen habe, habe ich mich jetzt Stunden dran gesetzt und versucht mal das ganze nach viel Überlegungen zu lösen.
Hier die Klassen nacheinander mit Beschreibung:
Das ist meine DTOWetter die im Prinzip nur das Object "wetter" abbildet.
Java:
public class DTOWetter {
private String temp;
private String nied;
private String wind;
public DTOWetter(String temp, String nied, String wind) {
this.temp = temp;
this.nied = nied;
this.wind = nied;
}
public String getTemp() {
return this.temp;
}
public String getNiederschlag() {
return this.nied;
}
public String getWind() {
return this.wind;
}
public void setTemp(String temp) {
this.temp = temp;
}
public void setNied(String nied) {
this.nied = nied;
}
public void setWind(String wind) {
this.wind = wind;
}
}
Hier im Code "DatenSetzen" hole ich meine Daten, aktuell ist es nur ein mehrdim. Array.
später folgen hier "richtige" Daten die ich von einer Z.B. Datenbank hole.
Java:
public class DatenSetzen {
private String[][] tArray = {{"14", "0L", "32kmh"},{"10", "5L", "4kmh"},{"21", "0L", "0kmh"}};
public String[][] getArray() {
return tArray;
}
}
In der klasse "Spalten" wandle ich die Array Strings in TableView verträgliche Daten um und gebe eine "ObservableList" zurück.
Java:
public class Spalten {
private ObservableList<DTOWetter> daten;
private DatenSetzen wet = new DatenSetzen();
public Spalten() {
daten = FXCollections.observableArrayList();
for (int i = 0; i < wet.getArray().length; i++){
daten.add(new DTOWetter(wet.getArray()[i][0], wet.getArray()[i][1], wet.getArray()[i][2]));
}
}
public ObservableList<DTOWetter> getOserv(){
return daten;
}
}
Und schlussendlich dann meine Anzeigeklasse
Java:
public class Anzeigefenster extends Stage{
Stage meinStage;
private Spalten data = new Spalten();
public Anzeigefenster(String x) throws IOException {
meinStage = this;
TableView<DTOWetter> table = new TableView<DTOWetter>();
TableColumn<DTOWetter, String> vorname = new TableColumn<DTOWetter, String>("vornameeee");
TableColumn<DTOWetter, String> nachname = new TableColumn<DTOWetter, String>("lastName");
TableColumn<DTOWetter, String> telefon = new TableColumn<DTOWetter, String>("tele");
vorname.setCellValueFactory(new PropertyValueFactory<DTOWetter, String>("temp"));
nachname.setCellValueFactory(new PropertyValueFactory<DTOWetter, String>("niederschlag"));
telefon.setCellValueFactory(new PropertyValueFactory<DTOWetter, String>("wind"));
table.setItems(data.getOserv());
table.getColumns().add(vorname);
table.getColumns().add(nachname);
table.getColumns().add(telefon);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
Scene scene = new Scene(new Group());
((Group) scene.getRoot()).getChildren().addAll(vbox);
meinStage.setTitle("Wetter");
meinStage.setWidth(800);
meinStage.setHeight(500);
meinStage.setScene(scene);
meinStage.show();
}
}
Ich hoffe ich habe es jetzt so richtig verstanden.
Jetzt werde ich mir den Code von mihe7 genauer ansehen.
Bitte stört euch nicht an den Variablen oder Klassennamen, das war von mir nur mal als Bespiel. wenn ich den Code überarbeite, dann setze ich auch aussagekräftige Namen ein.
Also nur um es noch einmal deutlich zu sagen: private String[][] tArray = {{"14", "0L", "32kmh"},{"10", "5L", "4kmh"},{"21", "0L", "0kmh"}};
Das ist absoluter Müll!
Wieso nutzt Du ein 2D Array? Der Wert an Index 0 scheint ein Integer zu sein, den Du als String gespeichert hast. Bei index 2 und 3 ggf. auch.
So Zugriffe mit [0] und so, sind doch nicht lesbar!
Du hast Da ganz offensichtlich 3 Werte. Und diese kannst Du entsprechend in Variablen Speichern. Dazu hast Du dann eine Klasse:
Java:
public class WeatherDetails {
private int temperature;
private int rainfall;
private int windSpeed; // So es das ist...
// Kondtruktoren / Getter / Setter weggelassen ...
}
Deine Daten sind dann ggf. einfach ein
Java:
List.of(
new SchrottDaten(14, 0, 32),
new SchrottDaten(10, 5, 4),
new SchrottDaten(21, 0, 0)
);
Damit hast Du die Daten sauber. Die Namen musst Du natürlich anpassen. Und dann kannst Du auch die Datentypen so wählen, wie Du es brauchst. Wenn etwas kein int ist, dann nimmst Du halt, was Du brauchst.
Und Dein Code danach passt auch nicht: new PropertyValueFactory<DTOWetter, String>("temp")`
erwartet, dass Du in einer Row die Daten aus einer Datenklasse DTOWetter, die dann temp hat mit einer Methode getTemp(), die ein String zurück gibt. Und das hast Du doch gar nicht!
Aber mir der Datenklasse, die ich genannt habe, hast Du die Daten einer row in einer Instanz und du hast dann die entsprechenden Getter, die Du angeben kannst.
Und dann hast Du die Werte als solche. Die sind dann in einer Einheit angegeben. Wenn die Einheit nicht überall gleich ist, dann hättest Du noch ein weiteres Feld dafür. Und bei der Anzeige hast Du dann ein Formatter, der die Zahl wie gewünscht anzeigt, also z.B. mit Tausenderpunkt. Und natürlich auch mit der Einheit (Liter ist dann auch l und nicht L),
Das wäre der Aufbau, den wir vorschlagen und den Du auch annehmen solltest.
Also ich habe jetzt nochmal den Code von mihe7 durchstudiert.
Jetzt wird mir so einiges klar.
Aber ich habe z.B. die Klasse"record" noch nicht gehört. Soweit reich mein Java Horizont leider noch nicht.
Es sind leider noch viele Dinge (Klassen und Methoden) die ich noch nicht kenne, und deswegen auch nicht anwenden kann.
record sind spezielle Klassen. In der Java Language Specification ist dies in 8.10 zu finden: Chapter 8. Classes (oracle.com)
record ganz in der Kürze
ein record kann nicht abstract oder sealed sein.
ein record kann nicht von irgend einer anderen Klasse erben sondern erbt immer von Record.
Du gibst einfach in runden Klammern die Member Fields des records an.
Einfach mal als Beispiel die WeatherDetails als record: public record WeatherDetails (int temperature, int rainfall, int windSpeed) {}
Du hast damit final Instanzvariablen temperature, rainfall und windSpeed.
Damit hast Du einen Konstruktor, der temperature, rainfall und windSpeed als Parameter entgegen nimmt.
Und Du hast für jedes Element Getter - aber ohne das "get", also temperatur(), rainfall() und windSpeed() wären die Getter.
Du hast keine Setter (bei final fields wäre das auch nicht möglich!).
Was Du aber auch hast sind:
equals, hashcode und toString Methoden.
Man kann da natürlich noch etwas mehr machen, also z.B. die vorgegebenen Methoden / Konstruktoren kannst Du überschreiben. Oder Du kannst noch Interfaces implementieren und so...
Vielen Dank für die ausführliche Beschreibung. Ich hab das auch mal in Oracle angeschaut, scheint auch ziemlich ne zu sein.
Wenn ich Daten ändern möchte nehme ich aber die "klassische" Variante mit einer Klasse die die Elemente im Konstruktor entgegen nimmt und diese Klasse auch Getter und Setter Methoden enthalten.
Genau. Klasse mit veränderlichem Zustand als class implementieren. Ich habe record nur verwendet, weil es mir Tipparbeit spart Bei mir ist WeatherData, was bei Dir WeatherDTO und bei @KonradN WeatherDetails heißt: schlicht ein Container für Daten.
Zunächst einmal hast Du Wetterdaten wie Temperatur, Windstärke und Niederschlag, vermutlich auch einen Zeitpunkt. Das kann man in einer entsprechenden Klasse oder mit einem Record darstellen:
Java:
public record WeatherData(LocalDateTime time, double temperature, int bft, double precipitation) {}
Die Wetterdaten möchtest Du in einer Tabelle anzeigen, also brauchst Du ein entsprechendes UI, in JavaFX wäre das wohl die TableView:
Java:
TableView<WeatherData> weatherTable = new TableView<>();
TableColumn<WeatherData, String> timeCol = new TableColumn<>("Zeit");
timeCol.setCellValueFactory(w -> new SimpleStringProperty(w.getValue().time().toString()));
TableColumn<WeatherData, Number> temperatureCol = new TableColumn<>("Temperatur");
temperatureCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().temperature()));
TableColumn<WeatherData, Number> bftCol = new TableColumn<>("bft");
bftCol.setCellValueFactory(w -> new SimpleIntegerProperty(w.getValue().bft()));
TableColumn<WeatherData, Number> precipitationCol = new TableColumn<>("Niederschlag");
precipitationCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().precipitation()));
weatherTable.getColumns().addAll(timeCol, temperatureCol, bftCol, precipitationCol);
Du siehst schon am Typparameter, dass die Tabelle Wetterdaten anzeigen wird. Soweit, so gut.
Wo kommen jetzt die Daten her? Es wird irgendetwas benötigt, das z. B. eine Liste aller Wetterdaten liefert. Das lässt sich einmal ganz abstrakt mit einem Interface darstellen:
Java:
public interface WeatherDataRepository {
List<WeatherData> findAll();
}
Das reicht schon, um die Tabelle mit Daten zu befüllen. Sagen wir mal die Variable repository wäre vom Typ WeatherDataRepository, dann könnten wir im UI schreiben:
Der Code würde - in entsprechenden Methoden - sogar kompilieren. Was noch fehlt ist die Implementierung des Repositories. Hier mal als Klasse:
Java:
class DummyWeatherDataRepository implements WeatherDataRepository {
@Override
public List<WeatherData> findAll() {
return List.of(
new WeatherData(LocalDateTime.parse("2024-02-27T00:00"), 3.2, 2, 0),
new WeatherData(LocalDateTime.parse("2024-02-27T01:00"), 3.4, 1, 1),
new WeatherData(LocalDateTime.parse("2024-02-27T02:00"), 4.1, 3, 3),
new WeatherData(LocalDateTime.parse("2024-02-27T03:00"), 5.0, 2, 0));
}
}
Wo findAll() die Daten herbekommt, spielt für den Rest der Anwendung natürlich keine Rolle. Deine Implementierung kann z. B. auf eine Datenbank zugreifen.
So, und damit haben wir auch schon alles beisammen:
Java:
public class Weather extends Application {
private WeatherDataRepository repository;
@Override
public void init() throws Exception {
repository = new DummyWeatherDataRepository();
}
@Override
public void start(Stage stage) throws Exception {
TableView<WeatherData> weatherTable = createWeatherDataTable();
weatherTable.getItems().addAll(repository.findAll());
Scene scene = new Scene(weatherTable);
stage.setScene(scene);
stage.show();
}
private TableView<WeatherData> createWeatherDataTable() {
TableView<WeatherData> weatherTable = new TableView<>();
TableColumn<WeatherData, String> timeCol = new TableColumn<>("Zeit");
timeCol.setCellValueFactory(w -> new SimpleStringProperty(w.getValue().time().toString()));
TableColumn<WeatherData, Number> temperatureCol = new TableColumn<>("Temperatur");
temperatureCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().temperature()));
TableColumn<WeatherData, Number> bftCol = new TableColumn<>("bft");
bftCol.setCellValueFactory(w -> new SimpleIntegerProperty(w.getValue().bft()));
TableColumn<WeatherData, Number> precipitationCol = new TableColumn<>("Niederschlag");
precipitationCol.setCellValueFactory(w -> new SimpleDoubleProperty(w.getValue().precipitation()));
weatherTable.getColumns().addAll(timeCol, temperatureCol, bftCol, precipitationCol);
return weatherTable;
}
public static void main(String[] args) {
Application.launch(args);
}
}
Eins ist mir noch aufgefallen, warum wird in der View Klasse mit "private WeatherDataRepository repository;" die "WeatherDataRepository" initialisiert und nicht gleich die "DummyWeatherDataRepository()" Klasse?
Beim durchprobieren habe ich mal gleich die DummyWeatherDataRepository() initialisiert, das hat auch funktioniert.
warum wird in der View Klasse mit "private WeatherDataRepository repository;" die "WeatherDataRepository" initialisiert und nicht gleich die "DummyWeatherDataRepository()" Klasse?
Du meinst die Deklaration (initialisiert wird die Instanzvariable repository im Standardkonstruktor erst einmal mit null und in init() wird ihr dann eine Instanz von DummyWeatherDataRepository zugewiesen).
Zunächst einmal könnte man auf das Interface WeatherDataRepository komplett verzichten. Stattdessen könnte man einfach in einer Klasse WeatherDataRepository implementieren, was man gerne hätte, z. B. eben eine fixe Liste von Wetterdaten oder eine Implementierung, die aus einem Excel-File liest oder eine, die eine Datenbank verwendet usw.
Wozu also überhaupt ein Interface?
Das Interface dient der Entkopplung. Der Code (mit Ausnahme der init-Methode) arbeitet gegen das Interface, kennt die konkrete Implementierung nicht und ist somit von der konkreten Implementierung vollständig entkoppelt. Wenn Du in der IDE die Autovervollständigung nutzt, werden Dir ausschließlich die Methoden des Interfaces angezeigt, nicht aber die Methoden, die die implementierende Klasse ggf. sonst noch anbietet.
Ein Interface ist wie ein Vertrag (daher nennt man dieses Vorgehen auch "design by contract", s. z. B. https://de.wikipedia.org/wiki/Design_by_Contract), in dem die Methoden sowie ihre Vor- und Nachbedingungen festgelegt sind. Die Klasse, die das Interface implementiert, garantiert die Einhaltung des Vertrags, wird also die Nachbedingungen erfüllen (sofern die Vorbedingungen erfüllt sind). Die Nutzer des Interfaces können sich also darauf verlassen, dass die Nachbedingungen erfüllt werden - wenn sie ihrerseits die Vorbedingungen erfüllen.
Es spielt also für den Code (mit Ausnahme der init-Methode) überhaupt keine Rolle, welche Implementierung die Daten liefert. Alles, was der Code wissen muss ist, dass es eine Methode findAll gibt, die eine Liste von WeatherData liefert.
Im Beispielcode wird an genau einer Stelle die konkrete Implementierung genannt: in der init-Methode. Das hat zwei Konsequenzen:
Um eine andere Implementierung zu verwenden, muss nur an dieser einen Stelle eine Änderung vorgenommen werden. An der Instanzvariablen muss nichts geändert werden (wenn Du die Instanzvariable als DummyWeatherDataRepository deklarierst und später eine andere Implementierung verwenden willst, müsstest Du auch den Typ der Instanzvariablen anpassen).
Man kann also die Entscheidung, welche Implementierung verwendet wird, sehr spät fällen. Wenn man wollte, könnte man das sogar erst zur Laufzeit ermitteln. Alles, was Du dazu tun müsstest, wäre z. B. die Kommandozeilenparameter auszuwerten und dann die entsprechende Implementierung (z. B. Fix für Test, Daten aus einem angegebenen Excel-File oder Daten aus einer SQL-Datenbank, Abruf über HTTP-Requests usw.) zu verwenden.
Und jetzt die provokante Gegenfrage: wenn ich mir schon die Arbeit eines "Entwurfs mit Verträgen" mache, warum sollte ich dann den Typ der Instanzvariablen repository auf DummyWeatherDataRepository festlegen?