问题
I do have an engine which contains parts in a list like:
UPDATE: i updated the text to provide an example
I would like to have a TableView of engines which contains 2 Columns (name, parts). I want the parts Column to be rendered as a ListView within the TableCell, therefore i override the CellFactory of the column.
import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty;
public class Part {
private final SimpleStringProperty name = new SimpleStringProperty("");
// Reference to the parent engine
private final ObjectProperty<Engine> engine= new SimpleObjectProperty<>(null);
public Part(String name, Engine engine) {
this.setName(name);
this.setEngine(engine);
}
public ObjectProperty<Engine> engineProperty() {
return engine;
}
public Engine getEngine() {
return engine.get();
}
public void setEngine(Engine engine) {
this.engine.set(engine);
}
public SimpleStringProperty nameProperty() {
return name;
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
} }
The Engine Class:
import javafx.beans.Observable;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.Callback;
public class Engine {
private final SimpleStringProperty name = new SimpleStringProperty("");
Callback<Part, Observable[]> partsExtractor = part -> new Observable[] {
part.nameProperty(),
part.engineProperty()
};
private final ObservableList<Part> parts = FXCollections.observableArrayList(partsExtractor);
public Engine(String name) {
this.setName(name);
}
public void setName(String name) {
this.name.set(name);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public ObservableList<Part> getParts() {
return parts;
}
}
The Application:
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
* If you edit the field name of the engine on the table the changed name does not reflect on the listView within the
* table cell, it should actually show the new name of the engine
*/
class PartListCell extends ListCell<Part> {
private Label name = new Label();
public PartListCell() {
}
@Override
protected void updateItem(Part part, boolean empty) {
super.updateItem(part, empty);
if(!empty && part != null) {
name.setText(part.getEngine().getName() + " / " + part.getName());
setGraphic(name);
} else {
setGraphic(null);
}
}
}
class PartsTableCell extends TableCell<Engine, ObservableList<Part>> {
private final ListView<Part> listView = new ListView<>();
public PartsTableCell() {
listView.setCellFactory(listView -> new PartListCell());
}
@Override
protected void updateItem(ObservableList<Part> parts, boolean empty) {
if(!empty && parts != null){
listView.setItems(parts);
setGraphic(listView);
} else {
setGraphic(null);
}
}
}
public class Main extends Application {
Callback<Engine, Observable[]> engineExtractor = engine -> new Observable[] {
engine.nameProperty(),
};
private final ObservableList<Engine> engines = FXCollections.observableArrayList(engineExtractor);
public Main() {
Engine engine = new Engine("Engine-1");
Part part1 = new Part("Part-1", engine);
Part part2 = new Part("Part-1", engine);
engine.getParts().addAll(part1, part2);
engines.add(engine);
}
@Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
TableColumn<Engine, String> nameColumn = new TableColumn<>("Name");
TableColumn<Engine, ObservableList<Part>> partsColumn = new TableColumn<>("Parts");
// Set the Cell Factories
nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
// Make an editable text field
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setOnEditCommit(event -> event.getTableView().getItems().get(
event.getTablePosition().getRow()).setName(event.getNewValue()));
partsColumn.setCellFactory(param -> new PartsTableCell());
partsColumn.setCellValueFactory(new PropertyValueFactory<>("parts"));
TableView<Engine> tableView = new TableView<>(engines);
tableView.setEditable(true);
tableView.getColumns().add(nameColumn);
tableView.getColumns().add(partsColumn);
// Set the stage
root.getChildren().add(tableView);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
}
But how do i create the CellValueFactory
, because a
partsColumn.setCellValueFactory(new PropertyValueFactory<>("parts"));
wont do it since it will get wrapped and if i change a part within the parts list it does not update the parts in the parts listView. I need to create a Callback which provides the list of parts as an observable to the Table Cell, but i do not know how to do this. Anyone for help?
回答1:
Not an answer, too long for a comment:
Still not quite certain if I understand your requirement correctly: but here's an extractor that does what I think you want:
Callback<Part, Observable[]> partsExtractor = part -> new Observable[] {
part.nameProperty(), part.engineProperty().get().nameProperty() };
回答2:
Your PartListCell
just sets the text of its label to the current value of the name of the engine concatenated with the name of the part. If the part displayed by the cell changes, it will update (updateItem(...)
will be called again), but neither the cell nor the label are observing the name of the engine. Consequently if the name of the engine changes, the cell does not know it needs to update.
The fix is just to bind the text of the label to an appropriate StringExpression
:
class PartListCell extends ListCell<Part> {
private Label name = new Label();
public PartListCell() {
}
@Override
protected void updateItem(Part part, boolean empty) {
super.updateItem(part, empty);
name.textProperty().unbind();
if(!empty && part != null) {
name.textProperty().bind(part.getEngine().nameProperty().concat(" / ").concat(part.nameProperty()));
setGraphic(name);
} else {
name.setText(null);
setGraphic(null);
}
}
}
来源:https://stackoverflow.com/questions/35608027/javafx-tableview-containing-listview-updates-cellvaluefactory