Issue
JavaFX: How do I update a selected Item from observableArrayList, in a new Scene. Without passing the in the entire set of data backing it?
I think it would make sense just to pass in the one selected item to the new window. I have unable to get the observableArrayList to reflect changes by doing this.
I have only been able to get my program to work by passing in the entire data set. Both the ArrayList backing the observableArrayList and the observableArrayList its self. Then finding the element in the ArrayList, modifying the element, re-inserting the element back into the list, and then purging and re-adding the entire ArrayList to the observableArrayList. I am sure this is not the best way to do this. Being new to Javafx I am a not sure how to make this work.
Here is a working example of how I have accomplished this to work. Hopefully, this highlights what I am doing wrong and what I need to change.
Please note: I do not want to use any static classes if possible. This is a small project that I plan to extend to a larger project and I want to avoid pitfalls of using static classes.
Please note: I am omitting posting imports. Assume I have the correct imports.
Visual reference of my example code
Directory Structure.
--example
main.fxml
Main.java
MainController.java
modify.fxml
ModifyController.java
myData.java
Main.java
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
ArrayList<myData> values = new ArrayList<>();
values.add(new myData("1"));
values.add(new myData("2"));
values.add(new myData("3"));
FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
Parent root = loader.load();
MainController myController = loader.getController();
myController.initialize(values);
primaryStage.setTitle("Hello World");
Scene mainScene = new Scene(root);
primaryStage.setScene(mainScene);
myController.setMainStage(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java
public class MainController {
@FXML
private Button modifyButton;
@FXML
private TableView<myData> valueTable;
@FXML
private TableColumn valueCell;
private ObservableList<myData> observableDataModels;
private ArrayList<myData> values;
private Stage primaryStage;
public void initialize(ArrayList<myData> values) {
this.values = values;
this.observableDataModels = FXCollections.observableArrayList(values);
this.valueCell.setCellValueFactory(new PropertyValueFactory<>("value"));
this.valueTable.setItems(observableDataModels);
}
public void setMainStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
public void onMouseClicked(MouseEvent mouseEvent) throws IOException {
if (!this.valueTable.getSelectionModel().getSelectedCells().isEmpty()) {
myData selectedItem = this.valueTable.getSelectionModel().getSelectedItem();
FXMLLoader loader = new FXMLLoader(getClass().getResource("modify.fxml"));
Parent root = loader.load();
ModifyController mpc = loader.getController();
mpc.setData(selectedItem);
mpc.setObservableDataModels(this.observableDataModels);
mpc.setValues(this.values);
TextField t = mpc.getValueTxtFld();
t.setText(selectedItem.getValue());
mpc.setValueTxtFld(t);
Stage modifyStage = new Stage();
Scene modfifyPartScene = new Scene(root);
modifyStage.setScene(modfifyPartScene);
mpc.setCurrStage(modifyStage);
modifyStage.show();
}
}
}
ModifyController.java
public class ModifyController {
@FXML
private TextField valueTxtFld;
@FXML
private Button mdfSaveBtn;
@FXML
private Stage currStage;
private myData data;
private ObservableList<myData> observableDataModels;
private ArrayList<myData> values;
public void setObservableDataModels(
ObservableList<myData> observableDataModels) {
this.observableDataModels = observableDataModels;
}
public ArrayList<myData> getValues() {
return values;
}
public void setValues(ArrayList<myData> values) {
this.values = values;
}
public TextField getValueTxtFld() {
return valueTxtFld;
}
public void setValueTxtFld(TextField valueTxtFld) {
this.valueTxtFld = valueTxtFld;
}
public void setData(myData selectedItem) {
this.data = selectedItem;
}
public void setCurrStage(Stage modifyStage) {
this.currStage = modifyStage;
}
public void onMouseClicked(MouseEvent mouseEvent) {
myData dataBuffer = this.data;
int i = dataBuffer.hashCode();
this.data.setValue(this.valueTxtFld.getText());
int count = 0;
for (myData d : values) {
if (i == d.hashCode()) {
values.remove(count);
values.set(count, this.data);
} else {
count += 1;
}
}
observableDataModels.removeAll(values);
observableDataModels.addAll(values);
this.currStage.close();
}
}
main.fxml
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="example.ModifyController">
<children>
<TextField fx:id="valueTxtFld" layoutX="193.0" layoutY="169.0" />
<Button fx:id="mdfSaveBtn" layoutX="261.0" layoutY="219.0" mnemonicParsing="false" onMouseClicked="#onMouseClicked" text="Save" />
</children>
</AnchorPane>
modify.fxml
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="example.ModifyController">
<children>
<TextField fx:id="valueTxtFld" layoutX="193.0" layoutY="169.0" />
<Button fx:id="mdfSaveBtn" layoutX="261.0" layoutY="219.0" mnemonicParsing="false" onMouseClicked="#onMouseClicked" text="Save" />
</children>
</AnchorPane>
Solution
You're editing a single item. You shouldn't a list for this reason. Stage.showAndWait
allows you to wait for the user to finish the input and react to it, which I strongly recommend doing.
There are several things I recomment changing in addition to this:
- Use
onAction
events for button clicks instead ofonMouseClicked
events. This way you'll also react to pressing the Enter key when the button is focused and prevents clicks with mouse buttons other than the primary one from triggering the button. - Use the
selectedItem
immediately instead of the selected cells. Those 2 are independent things and a check fornull
isn't really any harder than callingisEmpty()
on some list. - Do not provide direct access to nodes of the scene. This just spreads the responsibility for handling the nodes across multiple classes making the code harder to maintain. Furthermore e.g. the setter for the
valueTxtFld
property makes the code even more confusing, since you may end up with aTextField
that is not part of the scene: you could pass a differentTextField
than the onegetValueTxtFld
returns tosetValueTxtFld
resulting in an error that is not the simplest one to debug. If you'd simply provide access to the property you're using (theTextField.text
property), this wouldn't be a problem. In this case access is not required though. Do not modify a list while iterating though it. You may get a
ConcurrentModificationException
. Also do not substitute comparing hash codes for a check for equality. This is more complex than necessary and it may fail. (Assuming yourhashCode
implementation is based on theString
value alone there's a string for every possibleint
and there are strings that do don't containint
values so there must be 2 different objects with the same hashcode).
Simply useindexOf
instead:int index = values.indexOf(data); values.set(index, ...);
main.fxml
<Button fx:id="mdfSaveBtn" layoutX="261.0" layoutY="219.0" mnemonicParsing="false" onAction="#modify" text="Save" />
modify.fxml
<Button fx:id="mdfSaveBtn" layoutX="261.0" layoutY="219.0" mnemonicParsing="false" onAction="#save" text="Save" />
public class MainController {
...
// you can leave out the event parameter here, if you don't need it;
// otherwise be sure to use a parameter of type ActionEvent
@FXML
private void modify() throws IOException {
myData data = valueTable.getSelectionModel().getSelectedItem();
if (data != null) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("modify.fxml"));
Parent root = loader.load();
ModifyController mpc = loader.getController();
mpc.setData(data);
Stage modifyStage = new Stage();
Scene modfifyPartScene = new Scene(root);
modifyStage.setScene(modfifyPartScene);
modifyStage.showAndWait();
if (mpc.isEdited()) {
valueTable.refresh(); // or update the table by other means
}
}
}
}
public class ModifyController {
@FXML
private TextField valueTxtFld;
@FXML
private Button mdfSaveBtn;
private myData data;
// flag indicating, if the edit was submitted or not
private boolean edited;
public boolean isEdited() {
return edited;
}
public void setData(myData selectedItem) {
if (selectedItem == null) {
throw new IllegalArgumentException();
}
this.data = selectedItem;
valueTxtFld.setText(selectedItem.getValue());
edited = false;
}
public void save() {
data.setValue(valueTxtFld.getText());
edited = true;
mdfSaveBtn.getScene().getWindow().hide();
}
}
Answered By - fabian