JavaFX Tableview mit fxml ohne Aktualiserung trotz Thread

keinGuru67

Mitglied
Hallo!

Habe hier ein fxml JAVAFX Applikation die in einem Thread die Daten aktualisieren soll. Tatsächlich macht er gar nichts, nur wenn man auf Aufwärts Sort oder Abwärts Sort klickt erscheinen die Daten.
Ein ähnliches Bsp. ohne fxml funktioniert - was mache ich falsch?
Danke für Antworten.:shock:

Java:
package com.moho.lohnzettel.gui;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class StartMainFX extends Application {
	private Stage primaryStage;
	private Scene scene;

	public Stage getPrimaryStage() {
		return primaryStage;
	}

	public Scene getScene() {
		return scene;
	}

	private BorderPane rootLayout;

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

	// @Override
	public void start(Stage primaryStage) throws Exception {
		this.primaryStage = primaryStage;
		this.primaryStage.setTitle("Lohnzettel PDF");

		try {
			// Load the root layout from the fxml file
			FXMLLoader loader = new FXMLLoader(MenuController.class.getResource("view/TestFX.fxml"));
			rootLayout = (BorderPane) loader.load();
			scene = new Scene(rootLayout);
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	  
}

package com.moho.lohnzettel.gui;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;

import javafx.fxml.Initializable;

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

public class TestFX implements Initializable {
	@FXML
	private TableView<Data> pdfTable;
	@FXML
	private TableColumn<Data, String> fileNameColumn;

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		fileNameColumn.setCellValueFactory(new PropertyValueFactory<Data, String>("fileName"));
		start();
	}

	@FXML
	public void start() {
		for (int i = 0; i < 10; i++) {
			Data data = new Data();
			data.setFileName(i + " Before");
			pdfTable.getItems().add(data);
		}

		Thread threadAction1 = new Thread() {
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				}
				ObservableList<Data> items = pdfTable.getItems();
				for (int i = 0; i < items.size(); i++) {
					try {
						Thread.sleep(1000);
						Data d = items.get(i);
						System.out.println("test");
						d.setFileName("After " + i);

					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		};
		threadAction1.start();
	}

	public class Data {
		private SimpleStringProperty fileName;

		public String getFileName() {
			return fileName.get();
		}

		public Data() {
			super();
			this.fileName = new SimpleStringProperty();
		}

		public void setFileName(String fileName) {
			this.fileName.set(fileName);
		}

	}
}

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

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

<BorderPane prefHeight="265.0000999999975" prefWidth="282.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="com.moho.lohnzettel.gui.TestFX">
<center>
<TableView fx:id="pdfTable" prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn editable="false" minWidth="100.0" prefWidth="200.0" text="Filename" fx:id="fileNameColumn" />
</columns>
</TableView>
</center>
</BorderPane>
 
Ich schau mir das morgen damit dem Code von Arbeit an...da hab ich was ähnliches auch gemacht und es geht.
 
Ok, erste Frage: Du hast nur eine Spalte? Soll das so bleiben? Wenn ja, dann schwenke auf eine Liste um, das ist etwas leichter.
Wenn nein, dann geht's jetzt weiter.

Zum Vergleich mein FXML-Abschnitt:
[XML]
<TableView fx:id="tableResults" id="transparent-bg">
<minWidth><TableView fx:constant="USE_PREF_SIZE" /></minWidth>
<columns>
<TableColumn fx:id="tableColumnId" text="%root.table.column.seriesTest" prefWidth="110"/>
<TableColumn fx:id="tableColumnDate" text="%root.table.column.date" prefWidth="125"/>
<TableColumn fx:id="tableColumnDown" text="%root.table.column.down" prefWidth="100"/>
<TableColumn fx:id="tableColumnUp" text="%root.table.column.up" prefWidth="100"/>
<TableColumn fx:id="tableColumnRtt" text="%root.table.column.rtt" prefWidth="50"/>
</columns>
</TableView>
[/XML]
Ich lasse die Pref*-Sachen weg, aber das tut nichts zur Sache.

Ich habe gerade die Vermutung - da deine Daten ja schon da zu sein scheinen, dass dein erster Start über die initialize vielleicht zu früh ist. Bei mir kommen die Daten irgendwann zur Tabelle und erscheinen sofort, du machst es aber schon vom Start weg. Versuche das mal zu triggern, nachdem die UI auch angezeigt wird.

Irgendwie á la
Code:
((TestFX) loader.getController).start();
nachdem du
Code:
primaryStage.show();
ausgelöst hast.

Wenn das nicht hilft, dann mache auf jeden Fall folgendes:
Java:
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						// tableResults.getItems().add(details);
					}
				});
wann auch immer du etwas auslöst, dass die UI verändert, und du bist gerade nicht auf dem UI-Thread (sondern z.B. in deinem Thread, der aus "Before" ein "After" macht - kann man aus der Vorderansicht eine Rückansicht machen??? ;-) ), dann nutze diese Methode. Bläht den Code vielleicht auf, aber sischer ist sischer!

Sag mal bescheid, ob's klappt...
 
Hi!

Danke für die schnelle Antwort.
Das ist nur ein Beispiel Programm eines viel komplexeren Programms - ich brauche in der Tabelle 17 Spalten. Ich habe das Bsp--Programm umgebaut und das ganze mal mit Buttons umgestaltet - ein Button zum laden der Daten - einen zweiten Button um die Daten dann im Thread zu aktualisieren - allerdings macht er nur dann ein UPDATE von den neuen Daten wenn ich Abwärts und dann AUfwärts sortiere klicke. Ich habe auch deine Änderung implementiert mit den new Runnable allerdings - immer noch kein Update. Allerdings wenn das Fenster kleiner ist als die angezeigten Daten - und mann zieht dann das Fenster auf volle Grösse auf sieht man zumindest die Daten wurden verändert die ganze unten standen. Die Oberen sind weiterhin die alten.
Ich dachte, wenn man die ObservableList verwendet und in der Klasse auch die Simple....Property verwendet, muss die tableview das mitbekommen.

Keine Ahnung wo ich da einen Fehler habe - und wie vorher gesagt ohne fxml geht es ja.

lg mike


Java:
package com.moho.lohnzettel.gui;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

public class TestFX implements Initializable {
	@FXML
	private TableView<Data> pdfTable;
	@FXML
	private TableColumn<Data, String> fileNameColumn;
	@FXML
	private TableColumn<Data, String> fileNameColumn2;

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		fileNameColumn.setCellValueFactory(new PropertyValueFactory<Data, String>("fileName"));
		fileNameColumn2.setCellValueFactory(new PropertyValueFactory<Data, String>("fileName2"));
	}

	@FXML
	public void load() {
		for (int i = 0; i < 10; i++) {
			Data data = new Data();
			data.setFileName(i + " Before");
			data.setFileName2(i + " Before2");
			pdfTable.getItems().add(data);
		}
	}

	@FXML
	public void update2() {

		Thread threadAction1 = new Thread() {
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				}
				ObservableList<Data> items = pdfTable.getItems();
				for (int i = 0; i < items.size(); i++) {
					try {
						Thread.sleep(500);
						Data d = items.get(i);
						d.setFileName("After " + i);
						d.setFileName2("After2 " + i);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				System.out.println("Fertig");
			}
		};
		threadAction1.start();
	}
	
	
	@FXML
	public void update() {
		
		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				}
				ObservableList<Data> items = pdfTable.getItems();
				for (int i = 0; i < items.size(); i++) {
					try {
						Thread.sleep(500);
						Data d = items.get(i);
						d.setFileName("After " + i);
						d.setFileName2("After2 " + i);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				System.out.println("Fertig");

			}
		});

		
	}


	public class Data {
		private SimpleStringProperty fileName;
		private SimpleStringProperty fileName2;

		public String getFileName() {
			return fileName.get();
		}

		public String getFileName2() {
			return fileName2.get();
		}

		public Data() {
			super();
			this.fileName = new SimpleStringProperty();
			this.fileName2 = new SimpleStringProperty();
		}

		public void setFileName(String fileName) {
			this.fileName.set(fileName);
		}

		public void setFileName2(String fileName) {
			this.fileName2.set(fileName);
		}

	}
}

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

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

<BorderPane prefHeight="265.0000999999975" prefWidth="282.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="com.moho.lohnzettel.gui.TestFX">
  <center>
    <TableView fx:id="pdfTable" prefHeight="200.0" prefWidth="200.0">
      <columns>
        <TableColumn editable="false" minWidth="100.0" prefWidth="200.0" text="Filename" fx:id="fileNameColumn" />
        <TableColumn prefWidth="75.0" text="Filename2" fx:id="fileNameColumn2" />
      </columns>
    </TableView>
  </center>
  <top>
    <Pane prefHeight="57.0" prefWidth="282.0">
      <children>
        <Button layoutX="44.0" layoutY="15.0" mnemonicParsing="false" onAction="#load" text="Load" />
        <Button layoutX="168.0" layoutY="19.0" mnemonicParsing="false" onAction="#update" text="Update" />
      </children>
    </Pane>
  </top>
</BorderPane>
 
Also ein Erfahrungsbericht:
Das mit dem
Code:
Platform#runlater
hast du falsch rum verstanden. Wenn du aus einem nicht-UI-Thread, also z.B. deinem Thread zum updaten die UI anpassen möchtest, kapselst du genau diese Calls in
Code:
Platform#runlater
.
Also:
Java:
	@FXML
	public void update() {
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		ObservableList<Data> items = pdfTable.getItems();
		for (int i = 0; i < items.size(); i++) {
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				e.printStackTrace();
			}
			final int index = i;
			final Data d = items.get(i);
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					d.setFileName("After " + index);
					d.setFileName2("After2 " + index);
				}
			});
		}
		System.out.println("Fertig");
		
	}

Das Laden der Daten klappt bei mir - ich weiß jetzt nicht, ob das schon ein Problem bei dir war...

Das spricht jedenfalls dafür, das die ObservableList arbeitet, wie sie soll: Die Tabelle bwekommt mit, wenn neue Daten da sind und zeigt sie entsprechend an.

Dein Problem ist also rein das Update.

Ich habe es einmal mit dem Ansatz versucht, den ich verfolge, bin aber auch nicht weiter gekommen:
Java:
	@FXML
	private TableView<Data> pdfTable;
	@FXML
	private TableColumn<Data, Data> fileNameColumn;
	@FXML
	private TableColumn<Data, Data> fileNameColumn2;
	
	@Override
	public void initialize(URL location, ResourceBundle resources) {
		
		Callback<TableColumn.CellDataFeatures<Data, Data>, ObservableValue<Data>> contentCallback = new Callback<TableColumn.CellDataFeatures<Data, Data>, ObservableValue<Data>>() {
			@Override
			public ObservableValue<Data> call(CellDataFeatures<Data, Data> param) {
				return new SimpleObjectProperty<Data>(param.getValue());
			}
		};
		fileNameColumn.setCellValueFactory(contentCallback);
		fileNameColumn.setCellFactory(new Callback<TableColumn<Data, Data>, TableCell<Data, Data>>() {
			@Override
			public TableCell<Data, Data> call(TableColumn<Data, Data> param) {
				return new DataTableCell(0);
			}
		});
		fileNameColumn2.setCellValueFactory(contentCallback);
		fileNameColumn2.setCellFactory(new Callback<TableColumn<Data, Data>, TableCell<Data, Data>>() {
			@Override
			public TableCell<Data, Data> call(TableColumn<Data, Data> param) {
				return new DataTableCell(1);
			}
		});
	}
	
	public class DataTableCell extends TableCell<Data, Data> {
		
		private final int type;
		
		public DataTableCell(final int type) {
			this.type = type;
		}
		
		@Override
		protected void updateItem(Data item, boolean empty) {
			
			super.updateItem(item, empty);
			
			if (empty) {
				setText(null);
			} else {
				setTextOverrun(OverrunStyle.ELLIPSIS);
				String text = "";
				switch (type) {
				case 0:
					text = item.getFileName();
					break;
				case 1:
					text = item.getFileName2();
					break;
				}
				setText(text);
			}
		}
	}
Ich bin mir also immerhin nun sicher, dass es nichts mit den CellFactory's zu tun hat.

Bleibt das Binding übrig: Ich würde auch erwarten, dass die PropertyValueFactory gleich ein ordentliches Binding macht. Scheint aber nicht so.

Also hier meine Lösung: Die oben beschriebene DataTableCell habe ich leicht modifiziert, so dass folgende Lösung heraus kam.
Java:
	public class DataTableCell extends TableCell<Data, Data> {
		
		private final int type;
		
		public DataTableCell(final int type) {
			this.type = type;
		}
		
		@Override
		protected void updateItem(Data item, boolean empty) {
			
			super.updateItem(item, empty);
			
			if (empty) {
				textProperty().unbind();
				setText(null);
			} else {
				setTextOverrun(OverrunStyle.ELLIPSIS);
				switch (type) {
				case 0:
					textProperty().bind(item.fileName);
					break;
				case 1:
					textProperty().bind(item.fileName2);
					break;
				}
			}
		}
	}

Noch ein letztes: Du machst ein
Code:
Thread#sleep
in der Update-Methode. Das legt deinen UI-Thread schlafen und du erhälst eine Oberfläche, von der Windoof im schlimmsten Fall sagt: "Keine Rückmeldung!". Doof. also modifizieren wir diese Methode noch ein wenig:
Java:
	@FXML
	public void update() {
		
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException ex) {}
				ObservableList<Data> items = pdfTable.getItems();
				for (int i = 0; i < items.size(); i++) {
					try {
						Thread.sleep(500);
					} catch (Exception e) {}
					final int index = i;
					final Data d = items.get(i);
					Platform.runLater(new Runnable() {
						@Override
						public void run() {
							d.setFileName("After " + index);
							d.setFileName2("After2 " + index);
						}
					});
				}
				System.out.println("Fertig");
			}
		});
		t.start();
	}
(wir mir gerade auffällt, hast du ja nie auf einem anderen als dem UI-Thread gearbeitet - upps. Damit war in deiner Version das
Code:
Platform#runlater
eigentlich sinnlos. Bei meiner Variante ist es das aber nicht ;-) )

So. Und nun wieder frisch, fromm, fröhlich, frei ans Werk. Ich geh jetzt in den Feierabend! 🙂
 
Ich meinte schon, dass ich deine Version versuche - verstehe allerdings nicht - warum es einen unterschied macht zwischen fxml (also auslagern) oder alles in der klasse deklarieren mit der tableview usw. - das Beispiel mit dem Update mit einem Longtask hatte ich aus dem Internet - und das funktioniert - nur sobald ich es aus dem fxml starte - geht es nicht - ist das eventuell noch ein Bug?

Ich werden das morgen austesten - und melde mich dann - trotzdem herzlichst danke aus dem grünen (noch nicht verschneiten) Tirol - für deine Mühen.
Mike
 
Hallo!

Vielen Dank! Es wird jetzt aktualisiert - allerdings erst nach dem alles fertig ist - und nicht sofort wenn sich der Wert ändert -
hier ein Sample das funktioniert, aber ohne fxml
Java:
package com.moho.lohnzettel.gui;

import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;


import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.StackPaneBuilder;
import javafx.stage.Stage;

public class UpdateTableView extends Application implements Initializable {

	private TableView<Person> personTable = new TableView<>();
	private TableColumn<Person, String> colName = new TableColumn<Person, String>("name");
	private TableColumn colProgressBar = new TableColumn("Progress Bar");

	@Override
	public void start(Stage primaryStage) {
		showDialog(primaryStage);

		colName.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));
		//colProgressBar.setCellValueFactory(new PropertyValueFactory("progressBar")); 
		//colProgressBar.setCellFactory(ProgressBarTableCell.forTableColumn());
		//personTable.getColumns().addAll(colName, colProgressBar);
		personTable.getColumns().addAll(colName);

		for (int i = 0; i < 10; i++) {
			Person person = new Person();
			person.setProgressBar(-1.0);
			person.setName("Before" + i);
			personTable.getItems().add(person);
		}

		Thread threadAction1 = new Thread() {
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException ex) {
					Logger.getLogger(UpdateTableView.class.getName()).log(Level.SEVERE, null, ex);
				}
				ObservableList<Person> items = personTable.getItems();
				for (int i = 0; i < items.size(); i++) {
					try {
						Thread.sleep(1000);
						Person person = items.get(i);
						person.setName("After" + i);
						//person.setProgressBar(0.0);

					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		};
		threadAction1.start();
	}

	public void showDialog(Stage primaryStage) {
		try {
			StackPane root = StackPaneBuilder.create().children(personTable).build();
			primaryStage.setScene(new Scene(root, 300, 250));
			primaryStage.show();
		} catch (Exception ex) {
			System.out.println("Exception caught while showing dialog" + ex.getMessage());
		}
	}

	public static class Person {

		private StringProperty name;
		private DoubleProperty progressBar;

		private Person() {
			name = new SimpleStringProperty();
			progressBar = new SimpleDoubleProperty();
		}

		public Person(String name, double surname) {
			this.name = new SimpleStringProperty(name);
			this.progressBar = new SimpleDoubleProperty(surname);
		}

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

		public double getProgressBar() {
			return progressBar.get();
		}

		public void setProgressBar(double surname) {
			this.progressBar.set(surname);
		}

		public String getName() {
			return name.get();
		}

		public StringProperty nameProperty() {
			return name;
		}

		public DoubleProperty progressBarProperty() {
			return progressBar;
		}
	}

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

	@Override
	public void initialize(URL arg0, ResourceBundle arg1) {
		// TODO Auto-generated method stub
		
	}
	

}

Ich glaube es liegt am fxml - ich werden das ganze ohne dem fxml machen - trotzdem wäre es interessant - wo der Unterschied zwischen den zweien Arten der Programmierung liegt.
Danke für die Mühe
lg mike
 
Ich glaube der einzig wahre Unterschied ist, dass bei FXML, die Columns schon der Tabelle zugeordnet sind, bevor du die PropertyCellFactory ran hängst. Im Code dagegen machst du es anders herum.
Eine "Zwischenlösung" wäre, im FXML noch keine Columns zuzuordnen, sondern dass erst in der initialize-Methode zu machen (quasi genau so, wie du es bereits tust).

Ein Hinweis noch zur initialize-Methode:
Soweit ich weiss, ist das Interace Initializable "so gut wie deprecated" (siehe hier: Initializable (JavaFX 2.2) ). Du musst es also nicht verwenden, weil der FXMLLoader versucht die (anotierte?) Methode selbst zu finden. Ich verwende das Interface jedenfalls nicht mehr und annotiere nur eine initialize-Methode:
Code:
@FXML public void initialize() {...}
. Das klappt wunderbar.

Hast du eigentlich deine ProgressBar schon in deine Tabelle integriert? Ist mit der von mir geposteten Lösung eigentlich gar nicht schwer. Du musst da ja nur in etwa so etwas hier machen (falls du es nicht schon wusstest):
Java:
	private final class DeviceCellFactory implements Callback<ListView<Device>, ListCell<Device>> {
		@Override
		public ListCell<Device> call(ListView<Device> param) {
			final ListCell<Device> cell = new ListCell<Device>() {
				@Override
				protected void updateItem(Device item, boolean empty) {
					super.updateItem(item, empty);
					if (empty) {
						textProperty().unbind();
						setText(null);
						graphicProperty().unbind();
						setGraphic(null);
					} else {
						setTextOverrun(OverrunStyle.ELLIPSIS);
						// setText(getItem().getName());
						// textProperty().bind(item.name());
						textProperty().bind(new DeviceNameBinding(item));
						graphicProperty().bind(new DeviceDetectedBinding(item));
					}
				}
			};
			return cell;
		}
	}
	
	private final class DeviceNameBinding extends StringBinding {
		
		private final Device device;
		
		public DeviceNameBinding(Device device) {
			this.device = device;
			super.bind(device.name());
			super.bind(device.detected());
			super.bind(device.dataBytes());
			super.bind(device.interval());
		}
		
		@Override
		protected String computeValue() {
			String name = device.getName();
			String interval = device.isDetected() && device.getInterval() > 0 ? (device.getInterval() == 200 ? bundle
					.getString("configuration.confiurationUpdate.fiveHz") : device.getInterval() == 1000 ? bundle
					.getString("configuration.confiurationUpdate.oneHz") : "") : "";
			String dataUsage = device.isDetected() && device.getDataBytes() > 0 ? (device.getDataBytes() / InternalPreferences.DEVICE_SIZE_MIB) * 100 + "%"
					: "";
			return name + (device.isDetected() ? " (" + interval + ", " + dataUsage + ")" : "");
		}
	}
	
	private final class DeviceDetectedBinding extends ObjectBinding<Node> {
		
		private final Device device;
		
		public DeviceDetectedBinding(Device device) {
			this.device = device;
			super.bind(device.detected());
		}
		
		@Override
		protected Node computeValue() {
			ImageView iv = null;
			if (device.isDetected()) {
				iv = ImageResource.getImageView("fugue/tick-circle.png");
			} else {
				iv = ImageResource.getImageView("fugue/cross-circle.png");
			}
			return iv;
		}
	}
Der Unterschied ist: Ich habe es für einen ListView verwendet, daher sieht die erste Klasse etwas anders aus und ich kombiniere alles in einer "Zelle". Du kannst bei dir einfach den Text der Zelle unbinden und auf null setzen, danach im else-Zweig noch ein
Code:
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
einfügen, das überhaupt kein Text angezeigt wird. Und im Binding ganz unten gibst du statt eines ImageViews halt eine ProgressBar zurück. Fertig.

Lustig wird es dann, wenn du in deine Zellen "komplexere" Editoren integrierst. Ich brauchte mal einen inline-Editor für die oben verwendeten Devices, um ein Label zu ändern. Das sollte aber erst passieren, nachdem man per Ok-Button das bestätigt und ich wollte keinen separaten Dialog basteln. Das vorgehen dann ist
Code:
deviceTable.setEditable(true);
das selbe für die Column machen, die einen Editor haben soll und dann irgend etwas á la:
Java:
	public class EditDeviceIntervalTableCell extends TableCell<Device, Device> {
		
		private final ObservableMap<String, Long> comboBoxContent;
		
		private HBox hBox;
		private ComboBox<String> comboBox;
		private Button button;
		
		public EditDeviceIntervalTableCell() {
			Map<String, Long> content = new TreeMap<>();
			content.put(bundle.getString("configuration.confiurationUpdate.oneHz"), 1000L);
			content.put(bundle.getString("configuration.confiurationUpdate.fiveHz"), 200L);
			comboBoxContent = FXCollections.observableMap(content);
		}
		
		@Override
		public void startEdit() {
			super.startEdit();
			// if (isEmpty())
			if (isEmpty() || !getItem().isDetected())
				return;
			if (hBox == null) {
				createHBox();
			}
			comboBox.getSelectionModel().select(getText());
			button.setDisable(true);
			setGraphic(hBox);
			setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
			comboBox.requestFocus();
		}
		
		@Override
		public void cancelEdit() {
			super.cancelEdit();
			setContentDisplay(ContentDisplay.TEXT_ONLY);
		}
		
		@Override
		public void updateItem(Device item, boolean empty) {
			super.updateItem(item, empty);
			if (!isEmpty()) {
				textProperty().bind(new DeviceIntervalBinding(item, comboBoxContent));
				if (hBox != null) {
					comboBox.getSelectionModel().select(getText());
					button.setDisable(true);
				}
			} else {
				textProperty().unbind();
				setText(null);
				setGraphic(null);
			}
		}
		
		private void createHBox() {
			
			hBox = new HBox(5.0);
			comboBox = new ComboBox<>(FXCollections.observableArrayList(comboBoxContent.keySet()));
			button = new Button(bundle.getString("configuration.confiurationUpdate.button.ok"));
			button.setDisable(true);
			
			comboBox.setMaxWidth(Double.MAX_VALUE);
			HBox.setHgrow(comboBox, Priority.ALWAYS);
			HBox.setHgrow(button, Priority.NEVER);
			
			hBox.getChildren().add(comboBox);
			hBox.getChildren().add(button);
			
			comboBox.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(ActionEvent event) {
					if (!comboBox.getSelectionModel().getSelectedItem().equals(getText())) {
						button.setDisable(false);
					}
				}
			});
			button.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(ActionEvent event) {
					getItem().setInterval(comboBoxContent.get(comboBox.getSelectionModel().getSelectedItem()));
					
					ConfigureGPSDeviceTask task = new ConfigureGPSDeviceTask(broker, context.get(Stage.class), bundle, getItem());
					task.setCancelable(false);
					task.setTitleAsInitialStatus(true);
					UIEvent uiEvent = new UIEvent(UIBaseEvents.PROGRESS_ADD_TASK, task);
					broker.send(uiEvent.getName(), uiEvent);
					
					cancelEdit();
				}
			});
		}
	}
	
	private final class DeviceIntervalBinding extends StringBinding {
		
		private final ObservableMap<String, Long> comboBoxContent;
		
		private final Device device;
		
		public DeviceIntervalBinding(Device device, ObservableMap<String, Long> comboBoxContent) {
			super.bind(device.detected());
			super.bind(device.interval());
			this.device = device;
			this.comboBoxContent = comboBoxContent;
		}
		
		@Override
		protected String computeValue() {
			String interval = "";
			for (String key : comboBoxContent.keySet()) {
				if (comboBoxContent.get(key) == device.getInterval()) {
					interval = key;
					break;
				}
			}
			return device.isDetected() ? interval : "";
		}
	}
Zusammenfassend sieht man im Normalzustand nur einen Text (hier etwas wie 5Hz oder 1Hz), klickt man in die Zelle, schaltet sie auf "Graphic Only" und eine ComboBox und ein Button erscheint. Ändert man die Auswahl in der ComboBox, wird der Button aktiviert, klickt man OK, wird es übernommen, klickt man aber einfach woanders hin (ausserhalb der Zelle), wird wieder auf nur Text geschaltet und alles bleibt beim alten... Fand ich recht nett und hat mich inkl. Google und StackOverflow etwa eine halbe bin eine Stunde gekostet.
 
Hi!

Habe weiter probiert und bin heute auf die Lösung gestossen.
Mann muss in der Klasse unbedingt die set und getter haben plus die getter Methoden auf den Properties.

Java:
class Person{
SimpleString name;
String getName(){....}
setName(String name ){name.set(name)};
StringProperty nameProperty(){return name}

}

Nach dieser Umstellung hat es funktioniert!:idea:
lg mike
 

Zurück
Oben