Reputation: 1174
all!
While I designed my project on Java I encountered the following principal problem:
The program I need to write is interface that allows user to input some data and then save it in specific format. Within the project I store data in ArrayList<>
.
public class CQuestionnaire
{
// fields
private final List<CQuestStage> m_lstStages;
…
}
User interface is based on TableView. So I need ObservableList<>
to connect data and TableView
. As I think it will be good decision to link TableView
with the list from CQuestionnaire
directly. So I implemented getObservableList()
for this purpose:
public class CQuestionnaire
{
// fields
private final List<CQuestStage> m_lstStages;
public ObservableList<CQuestStage> getObservableList()
{
return FXCollections.observableArrayList(m_lstStages);
}
…
}
This doesn’t work because FXCollections.observableArrayList()
returns copy of the list and all user’s actions don’t change on CQuestionnaire
member.
The question is the following: is there any approach how to link TableView
with the ArrayList<>
without changing its type to ObservableList<>
(to leave data and its view separately) and without creating distinct list that is necessary for TableView
only?
Of course I’ve read this topic (JavaFX, Casting ArrayList to ObservableList). But the problem is that FXCollections.observableArrayList()
copies list there is no link between source list in CQuestionnaire
and TableView
.
Upvotes: 0
Views: 7447
Reputation: 209339
Use FXCollections.observableList(m_lstStages)
, which returns an observable list backed by the specified list, instead of FXCollections.observableArrayList(m_lstStages)
, which creates a new observable array list and adds the content of the collection to it.
That way, changes made to the content of the table view will automatically propagate back to the original list. (Note though, that while changes to the underlying list will change the observable list, since the underlying list is not observable there is no way for the table to be informed of such changes, so it can't automatically update.)
Note there is no real need to add this functionality to your model class; you can just wrap the list with FXCollections.observableList(...)
at the point you need it. This prevents your model having a dependency on the JavaFX API, which I assume is part of the aim here.
Here's a simple SSCCE.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.value.ObservableValueBase;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Spinner;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class TableWithModelWithPlainList extends Application {
@Override
public void start(Stage primaryStage) {
Model model = new Model();
for (int i = 1 ; i <= 20 ; i++) {
model.getItems().add(new Item("Item "+i, i*i));
}
TableView<Item> table = new TableView<>();
// use observable list wrapping model data for table items:
table.setItems(FXCollections.observableList(model.getItems()));
// normal table setup:
table.getColumns().add(column("Item", Item::getName));
table.getColumns().add(column("Value", Item::getValue));
// controls to modify table data:
TextField itemNameField = new TextField();
Spinner<Integer> spinner = new Spinner<>(1, 1000, 200);
Button add = new Button("Add");
EventHandler<ActionEvent> addHandler = e -> {
table.getItems().add(new Item(itemNameField.getText(), spinner.getValue()));
itemNameField.clear();
};
add.setOnAction(addHandler);
itemNameField.setOnAction(addHandler);
Button remove = new Button("Remove");
remove.disableProperty().bind(table.getSelectionModel().selectedItemProperty().isNull());
remove.setOnAction(e -> table.getItems().remove(table.getSelectionModel().getSelectedIndex()));
// checks that model and table are in sync:
Button debug = new Button("Debug");
debug.setOnAction(e -> {
System.out.println("Model has "+model.getItems().size()+" items; table has "+table.getItems().size()+" items");
for (int i = 0 ; i < model.getItems().size() && i < table.getItems().size(); i++) {
Item modelItem = model.getItems().get(i);
Item tableItem = table.getItems().get(i);
System.out.printf("Index %d: model %s (%d); table %s (%d); equal: %s%n",
i, modelItem.getName(), modelItem.getValue(),
tableItem.getName(), tableItem.getValue(),
tableItem.getValue()==modelItem.getValue() && tableItem.getName().equals(modelItem.getName()));
}
System.out.println();
});
// layout stuff:
HBox controls = new HBox(2, itemNameField, spinner, add, remove, debug);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(10));
BorderPane root = new BorderPane(table);
root.setBottom(controls);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S,T> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> new ObservableValueBase<T>() {
@Override
public T getValue() {
return property.apply(cellData.getValue());
};
});
return col ;
}
public static class Model {
private List<Item> items ;
public Model() {
this.items = new ArrayList<>();
}
public List<Item> getItems() {
return items ;
}
}
public static class Item {
private String name ;
private int value ;
public Item(String name, int value) {
this.name = name ;
this.value = value ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public static void main(String[] args) {
launch(args);
}
}
Upvotes: 6
Reputation: 175
You can add a listener which you can use to update the ArrayList. https://docs.oracle.com/javafx/2/api/javafx/collections/ObservableList.html#addListener(javafx.collections.ListChangeListener)
Upvotes: 1