Tabelle aktualisieren über anderen Thread

Guten Abend

Ich habe in JavaFX eine Tabelle an eine Liste Bidirectional gebunden und befülle diese Liste in FXThread. Dies funktioniert soweit wunderbar.

Nun bekomme ich über einen anderen Thread Informationen, mit denen ich die Tabelle aktualisieren muss. Jedoch ändern die Werte in der Tabelle nicht, obwohl sie in der Liste
geändert sind.

Kann es sein das die Bindings nur innerhalb des FX Thread funktionieren?

Wenn ja, wie kann ich dies umgehen? Denn so geht es auch nicht:


				@Override
				public void run() {
					// änderungen an der Tabelle
				}
			});```

Vielen Dank schonmal

Schon etwas länger her, dass ich mit JavaFX rumgespielt habe - aber ich meine ja: die bindings funktionieren nur im FX-Thread (sollte glaub auch eine entsprechend Exception geworfen werden). Wenn ich mich richtig entsinne, hatte ich mir für eine Liste extra einen Wrapper geschrieben gehabt, der vor einer “kritischen” Aktion mit Platform.isFxApplicationThread() erstmal geprüft hat - ob aus dem richtigen Thread operiert wird. Ansonsten wurde die Änderung über Platform.runLater realisiert. Hatte zumindest bei mir funktioniert gehabt.

Da aber dein Platform.runLater nicht zu funktionieren scheint: Wirft deine Anwendung irgendwelche Fehler?

Nein sie wirft nichts…

Vielleicht rufe ich Platform.runLater am falschen Ort auf… Mit dem hatte ich auch schon Probleme…

Naja heute Abend mal ein KSB machen…

Ich kann es nachvollziehen…

Sobald ich zu der Liste etwas hinzufüge aktualisiert sich der Wert in der Tabelle. Wenn ich jedoch einen vorhanden Wert in der Liste ändere, kriegt das die Tabelle nicht mit…

Ich Poste mal mein “kleines” Program:

Main:


import java.io.IOException;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

	// Hauptfenster
	private Stage stageHauptfenster;
	private BorderPane layoutHauptfenster;
	private Scene scene;

	private VerwListe verwListe;
	private Task tabFuellen;
	
	private Thread temp;

	/**
	 * Konstruktor
	 */
	public Main() {
		System.out.println("Konstruktor");

		verwListe = new VerwListe();
		
		tabFuellen = new TabFuellen(verwListe);
		
		temp = new Thread(tabFuellen);
		temp.start();
	}

	@Override
	public void start(Stage stageHauptfenster) throws Exception {
		this.stageHauptfenster = stageHauptfenster;

		System.out.println("Start");

		setUserAgentStylesheet(STYLESHEET_MODENA);

		initHauptfenster();
	}

	/**
	 * Initialisiert das Hauptfenster
	 */
	private void initHauptfenster() {

		stageHauptfenster.setTitle("Test Tabelle");

		// Load the root layout from the fxml file
		FXMLLoader loader = new FXMLLoader(Main.class.getResource("Tabelle.fxml"));

		try {
			layoutHauptfenster = (BorderPane) loader.load();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		// Controller für Hauptfenster
		Cont_Tabelle controler = loader.getController();

		scene = new Scene(layoutHauptfenster);
		stageHauptfenster.setScene(scene);

		// Anzeigen
		stageHauptfenster.show();

		// Verwaltung GUI
		controler.setVerwListe(verwListe);

	}

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

Verwaltung der Liste:
```package TabelleAktual;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class VerwListe {

	private ObservableList<Liste> listeArtikel = FXCollections.observableArrayList();

	private ObjectProperty<ObservableList<Liste>> clients = new SimpleObjectProperty<ObservableList<Liste>>();

	
	/**
	 * Konstruktor
	 */
	public VerwListe() {
		clients.set(listeArtikel);
	}
	
	/**
	 * 
	 */
	public void fillListe() {
		System.out.println("FillListe");
		Liste l = new Liste(0, 5);
		
		listeArtikel.add(l);
		
	}
	
	/**
	 * 
	 */
	public void cheangeValue() {
		System.out.println("Change Value");
		listeArtikel.get(0).setErledigt(true);
	}
	
	/**
	 * Uebergibt die Aenderungen an die Clients
	 * 
	 * @return Clients der Kontaktliste
	 */
	public ObjectProperty<ObservableList<Liste>> getClientsProperty() {
		return clients;
	}
}

Liste:


import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;

public class Liste {

	private SimpleIntegerProperty ziel;

	private SimpleIntegerProperty anzahl;

	private SimpleBooleanProperty erledigt;

	/**
	 * Uebergibt das Ziel und die Anzahl pro Artikel
	 * 
	 * @param ziel
	 * @param anzahl
	 */
	public Liste(int ziel, int anzahl) {
		setZiel(ziel);
		setAnzahl(anzahl);
		setErledigt(false);
	}

	/**
	 * @return Anzahl Stücke pro Auftrag
	 */
	public int getAnzahl() {
		return anzahl.get();
	}

	/**
	 * @param Anzahl
	 *            Stücke
	 */
	public void setAnzahl(int anzahl) {
		if (this.anzahl == null) {
			this.anzahl = new SimpleIntegerProperty(anzahl);
		} else {
			this.anzahl.set(anzahl);
		}
	}

	/**
	 * @return Das Ziel Des Auftrages
	 */
	public int getZiel() {
		return ziel.get();
	}

	/**
	 * @param Das
	 *            Ziel
	 */
	public void setZiel(int ziel) {
		if (this.ziel == null) {
			this.ziel = new SimpleIntegerProperty(ziel);
		} else {
			this.ziel.set(ziel);
		}
	}

	/**
	 * @return Setzt den Status auf erledigt
	 */
	public boolean isErledigt() {
		return erledigt.get();
	}

	/**
	 * Dieses Bit ist gesetzt wenn der Roboter die gewünschte Anzahl Artikel
	 * abgelegt hat
	 * 
	 * @param Gibt
	 *            den Status zurück
	 */
	public void setErledigt(boolean erledigt) {
		if (this.erledigt == null) {
			this.erledigt = new SimpleBooleanProperty(erledigt);
		} else {
			this.erledigt.set(erledigt);
		}
	}
	
}

Task Tabelle füllen:


import javafx.concurrent.Task;

public class TabFuellen extends Task {

	private VerwListe verwListe;

	public TabFuellen(VerwListe verwListe) {
		this.verwListe = verwListe;
	}

	@Override
	protected Object call() throws Exception {

		while (true) {
			Thread.sleep(2500);

			verwListe.fillListe();

			Thread.sleep(500);
			
			verwListe.cheangeValue();
		}
	}

}

Controller der View:


import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

public class Cont_Tabelle {

	private VerwListe verwListe;
	
	@FXML
	private TableView<Liste> tabelle;

	@FXML
	private TableColumn<Liste, Integer> colZiel;

	@FXML
	private TableColumn<Liste, Integer> colAnzahl;

	@FXML
	private TableColumn<Liste, Boolean> colStatus;

	/**
	 * Wird nach dem Start der GUI ausgeführt
	 */
	@FXML
	private void initialize() {

		colZiel = new TableColumn<Liste, Integer>();
		colZiel.setCellValueFactory(new PropertyValueFactory<Liste, Integer>("ziel"));
		colZiel.setMinWidth(150);
		colZiel.setText("Ziel");

		colAnzahl = new TableColumn<Liste, Integer>();
		colAnzahl.setCellValueFactory(new PropertyValueFactory<Liste, Integer>("anzahl"));
		colAnzahl.setMinWidth(150);
		colAnzahl.setText("Anzahl");

		colStatus = new TableColumn<Liste, Boolean>();
		colStatus.setCellValueFactory(new PropertyValueFactory<Liste, Boolean>("erledigt"));
		colStatus.setMinWidth(150);
		colStatus.setText("Status");

		tabelle.getColumns().addAll(colZiel, colAnzahl, colStatus);
	}
	
	/**
	 * 
	 * @param verwListe
	 */
	public void setVerwListe(VerwListe verwListe) {
		this.verwListe = verwListe;

		tabelle.itemsProperty().bindBidirectional(this.verwListe.getClientsProperty());
		
		//this.verwListe.fillListe();
	}
}

View:

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

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

<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="TabelleAktual.Cont_Tabelle">
   <top>
      <Label text="Test Tabelle mit externem Thread" BorderPane.alignment="CENTER" />
   </top>
   <center>
      <TableView fx:id="tabelle" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

Ok, ich denke ich hab deinen Fehler. Dir fehlen im Model Methoden. Nämlich die, die das Property-Objekt zurückgeben. Ohne dieses Objekt kann aber kein Databinding statt finden. Hier mal ein KSKB:

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
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.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;

public class Demo extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    public static class Model {
        private StringProperty beschreibung = new SimpleStringProperty(null);
        private BooleanProperty erledigt = new SimpleBooleanProperty(false);

        public String getBeschreibung() {
            return beschreibung.get();
        }

        // Kommentiere diese Methode aus - und du hast den gleichen Fehler wie bei dir
        public StringProperty beschreibungProperty() {
            return beschreibung;
        }
        

        public void setBeschreibung(String beschreibung) {
            this.beschreibung.set(beschreibung);
        }

        public boolean getErledigt() {
            return erledigt.get();
        }

        public BooleanProperty erledigtProperty() {
            return erledigt;
        }

        public void setErledigt(boolean erledigt) {
            this.erledigt.set(erledigt);
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        ObservableList<Model> model = FXCollections.observableArrayList();

        TableView tableView = new TableView();
        tableView.setItems(model);

        TableColumn<Model, String> beschColumn = new TableColumn<Model, String>();
        beschColumn.setCellValueFactory(new PropertyValueFactory<Model, String>("beschreibung"));

        TableColumn<Model, Boolean> erlColumn = new TableColumn<Model, Boolean>();
        erlColumn.setCellValueFactory(new PropertyValueFactory<Model, Boolean>("erledigt"));

        tableView.getColumns().addAll(beschColumn, erlColumn);

        Model e = create("Aufgabe A", false);

        Thread th = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            e.setBeschreibung("Geändert!");
        });
        th.setDaemon(true);
        th.start();

        model.add(e);
        model.add(create("Aufgabe B", true));

        Scene scene = new Scene(tableView, 300, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Model create(String besch, boolean erledigt) {
        Model model = new Model();
        model.setBeschreibung(besch);
        model.setErledigt(erledigt);
        return model;
    }
}

Wenn du das startest, wird die Beschreibung nach 3 Sekunden nach Programmstart geändert. Kommentierst du jetzt die Methode beschreibungProperty() aus (oder löscht sie), dann funktioniert es nicht mehr.

Ich war gerade an diesem Tutorial:

JavaFX 8 Tutorial - Part 2: Model and TableView | code.makery.ch

Da ist exakt das beschrieben was du mir jetzt gesagt hast! Es funktioniert wunderbar vielen Dank dafür!

Wieso geht es jedoch nur wenn ich die Methode genau so benenne

public BooleanProperty erledigtProperty()

Sobald ich der einen anderen Namen gebe geht es nicht mehr…

Ganz einfach. Er generiert sich den Methodennamen: {Name der Property}Property() und sucht danach. Gibt es diese Methode, dann verwendet er diese - ansonsten eben nicht. Der Name ist halt von JavaFX eine vorgegebene Konvention. Ich weiß nicht wie es bei anderen IDEs ausschaut - aber Intellij idea z.B. generiert dir diese Methoden automatisch mit [wenn du dir die Getter+Setter von der IDE erstellen lässt].

Achso, das werde ich mit Eclipse auch versuchen. Vielleicht generiert es diese auch automatisch…

Vielen Dank für deine super Hilfe!