2

I do have an engine which contains parts in a list like:

enter image description here

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?

pfried
  • 5,000
  • 2
  • 38
  • 71
  • hmm ... probably don't quite understand your setup: what is the type of the items of the table? Looks like something LineItem(Engine, Parts)? If so, you'll need an extractor on the _table.items_ list, that listens to the changes of the parts. If not, the ol' procedure: please provide a SSCCE that demonstrates the problem :-) – kleopatra Feb 24 '16 at 19:26
  • @kleopatra i provided an example above, unfortunately i had to create 2 additional files because i got a reflection error when i created the Property Value factory – pfried Feb 25 '16 at 07:22
  • unrelated: you don't need the commitHandler - automagics do it if the property is writable – kleopatra Feb 25 '16 at 10:41

2 Answers2

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);
        }
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you for your answer, i provided an example above to show what my problem is, i think i did not make it clear before – pfried Feb 25 '16 at 07:23
  • OK, I get it now: I didn't realize the list cell needed to display the name of the engine as well. See update. – James_D Feb 25 '16 at 13:48
  • Thanks James for the answer, i think kleopatra's solution is what i want even if your solution basically does it more effective. But if i can avoid doing the bindings on such a detailled level i would go for the extractor – pfried Feb 25 '16 at 14:31
  • Oh, that does work nicely. Hadn't looked carefully at it. – James_D Feb 25 '16 at 14:41
2

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() };
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • yes, thats basically it, i was not aware, that i can get parent properties with `.get()`, it works like i want it. – pfried Feb 25 '16 at 14:29