bazi
bazi

Reputation: 1866

javafx : how to programmatically change items of ComboBox in TableCell?

enter image description here

as you can see already, i have a TableView which have two columns of ComboBoxTableCell.

these are the ancestor values

ObservableList<String> ancestors = FXCollections.observableArrayList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
ancestorColumn.setCellFactory(ComboBoxTableCell.forTableColumn(ancestors));
descendantColumn.setCellFactory(ComboBoxTableCell.forTableColumn());

Map<String, List<String>> descendantMap = new HashMap();
descendantMap.put("1", Arrays.asList("A", "B", "C"));
descendantMap.put("2", Arrays.asList("Z", "L", "C"));
descendantMap.put("3", Arrays.asList("A", "B", "R"));
descendantMap.put("4", Arrays.asList("C", "B", "E"));
descendantMap.put("5", Arrays.asList("A", "E", "C"));
descendantMap.put("6", Arrays.asList("M", "V", "T"));
descendantMap.put("7", Arrays.asList("A", "G", "F"));
descendantMap.put("8", Arrays.asList("J", "O", "N"));
descendantMap.put("9", Arrays.asList("X", "G", "E"));
descendantMap.put("10", Arrays.asList("H", "I", "J"));

now when i change selection of ancestor combobox, the respective descendant combobox should get filled with items in the map of that key. ie, if i change ancestor to "1" on a table row, the descendant combobox on the same row should get filled with ("A", "B", "C). how to achieve this?

EDIT: hi, i've done it and it works. but im not sure if its the right way to do it. can someone tell me if this is the right way to do it?

descendantColumn.setCellFactory(param -> {
    return new TableCell() {
        ComboBox box = new ComboBox();
        @Override
        public void updateItem(String item, boolean empty) {
            if(!empty) {
                setGraphic(box);
                ancestorColumn.getCellObservableValue(getIndex()).addListener((o, ov, nv) -> {
                    if(nv != null) {
                        box.setItems(descendantMap.get(nv));
                    }
                });
            } else {
                setGraphic(null);
            }
        }
    }; 
});

Upvotes: 2

Views: 4179

Answers (1)

dzim
dzim

Reputation: 1153

I've created an example of how I would do that. But please: I build this within half an hour or so - the code is far from perfect, ok?

Here is the full code...

FXML:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
    Do not edit this file it is generated by e(fx)clipse from ../src/application/TableTest.fxgraph
-->

<?import java.lang.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane xmlns:fx="http://javafx.com/fxml" fx:controller="application.TableTestController">

    <center>
        <TableView fx:id="tableView"> 
            <columns>
                <TableColumn fx:id="tableColumnText" text="Text" prefWidth="150"/> 
                <TableColumn fx:id="tableColumnFlag" text="Flag" prefWidth="50"/> 
                <TableColumn fx:id="tableColumnAncestor" text="Ancestor" prefWidth="75"/> 
                <TableColumn fx:id="tableColumnDescendant" text="Descendant" prefWidth="100"/> 
                <TableColumn fx:id="tableColumnPoints" text="Points" prefWidth="75"/> 
            </columns>
            <BorderPane.margin>
                <Insets top="5" left="5" right="5" bottom="5"/> 
            </BorderPane.margin>
        </TableView>
    </center>
</BorderPane>

Model data class (the one, that is representing a single line in the TableView):

package application;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class TableTestObject {

    private StringProperty text = new SimpleStringProperty("");
    private BooleanProperty flag = new SimpleBooleanProperty(false);
    private StringProperty ancestor = new SimpleStringProperty("");
    private StringProperty descendant = new SimpleStringProperty("");
    private IntegerProperty points = new SimpleIntegerProperty(0);

    public final StringProperty textProperty() {
        return this.text;
    }

    public final String getText() {
        return this.textProperty().get();
    }

    public final void setText(final String text) {
        this.textProperty().set(text);
    }

    public final BooleanProperty flagProperty() {
        return this.flag;
    }

    public final boolean isFlag() {
        return this.flagProperty().get();
    }

    public final void setFlag(final boolean flag) {
        this.flagProperty().set(flag);
    }

    public final StringProperty ancestorProperty() {
        return this.ancestor;
    }

    public final String getAncestor() {
        return this.ancestorProperty().get();
    }

    public final void setAncestor(final String ancestor) {
        this.ancestorProperty().set(ancestor);
    }

    public final StringProperty descendantProperty() {
        return this.descendant;
    }

    public final String getDescendant() {
        return this.descendantProperty().get();
    }

    public final void setDescendant(final String descendant) {
        this.descendantProperty().set(descendant);
    }

    public final IntegerProperty pointsProperty() {
        return this.points;
    }

    public final int getPoints() {
        return this.pointsProperty().get();
    }

    public final void setPoints(final int points) {
        this.pointsProperty().set(points);
    }
}

Main class:

package application;

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

public class Main extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("TableTest.fxml"));
        Parent root = loader.load();
        Scene scene = new Scene(root, 1200, 800);
        stage.setScene(scene);
        stage.show();
    }

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

TableTestController (the one, that is doing the binding stuff):

package application;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

public class TableTestController {

    @FXML
    private TableView<TableTestObject> tableView;
    @FXML
    private TableColumn<TableTestObject, String> tableColumnText;
    @FXML
    private TableColumn<TableTestObject, TableTestObject> tableColumnFlag;
    @FXML
    private TableColumn<TableTestObject, TableTestObject> tableColumnAncestor;
    @FXML
    private TableColumn<TableTestObject, TableTestObject> tableColumnDescendant;
    @FXML
    private TableColumn<TableTestObject, TableTestObject> tableColumnPoints;

    private Map<String, List<String>> descendantMap;
    private ObservableList<String> ancestorList;

    @FXML
    protected void initialize() {

        descendantMap = new HashMap<>();
        descendantMap.put("1", Arrays.asList("A", "B", "C"));
        descendantMap.put("2", Arrays.asList("Z", "L", "C"));
        descendantMap.put("3", Arrays.asList("A", "B", "R"));
        descendantMap.put("4", Arrays.asList("C", "B", "E"));
        descendantMap.put("5", Arrays.asList("A", "E", "C"));
        descendantMap.put("6", Arrays.asList("M", "V", "T"));
        descendantMap.put("7", Arrays.asList("A", "G", "F"));
        descendantMap.put("8", Arrays.asList("J", "O", "N"));
        descendantMap.put("9", Arrays.asList("X", "G", "E"));
        descendantMap.put("10", Arrays.asList("H", "I", "J"));

        ancestorList = FXCollections.observableArrayList(descendantMap.keySet());

        ObservableList<TableTestObject> data = FXCollections.observableArrayList();
        TableTestObject test = new TableTestObject();
        test.setText("Test");
        test.setFlag(true);
        test.setPoints(1);
        data.add(test);

        tableView.setItems(data);

        tableColumnText.setCellValueFactory(new PropertyValueFactory<>("text"));
        tableColumnFlag.setCellValueFactory(param -> new SimpleObjectProperty<TableTestObject>(param.getValue()));
        tableColumnAncestor.setCellValueFactory(param -> new SimpleObjectProperty<TableTestObject>(param.getValue()));
        tableColumnDescendant.setCellValueFactory(param -> new SimpleObjectProperty<TableTestObject>(param.getValue()));
        tableColumnPoints.setCellValueFactory(param -> new SimpleObjectProperty<TableTestObject>(param.getValue()));

        tableColumnFlag.setCellFactory(param -> new TableCell<TableTestObject, TableTestObject>() {
            @Override
            protected void updateItem(TableTestObject item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setText(null);
                } else {
                    CheckBox cb = new CheckBox();
                    cb.selectedProperty().bindBidirectional(item.flagProperty());
                    setGraphic(cb);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                }
            }
        });
        tableColumnAncestor.setCellFactory(param -> new TableCell<TableTestObject, TableTestObject>() {
            @Override
            protected void updateItem(TableTestObject item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setText(null);
                } else {
                    ComboBox<String> cb = new ComboBox<>(ancestorList);
                    cb.getSelectionModel().selectedItemProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
                        item.ancestorProperty().set(newValue);
                        System.out.println(item.descendantProperty().get());
                    });
                    setGraphic(cb);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                }
            }
        });
        tableColumnDescendant.setCellFactory(param -> new TableCell<TableTestObject, TableTestObject>() {
            @Override
            protected void updateItem(TableTestObject item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setText(null);
                } else {
                    ComboBox<String> cb = new ComboBox<>();
                    item.ancestorProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
                        item.descendantProperty().set("");
                        cb.setItems(FXCollections.observableArrayList(descendantMap.get(newValue)));
                    });
                    cb.getSelectionModel().selectedItemProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
                        item.descendantProperty().set(newValue);
                        System.out.println(item.descendantProperty().get());
                    });
                    setGraphic(cb);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                }
            }
        });
        tableColumnPoints.setCellFactory(param -> new TableCell<TableTestObject, TableTestObject>() {
            @Override
            protected void updateItem(TableTestObject item, boolean empty) {
                super.updateItem(item, empty);
                if (empty) {
                    setText(null);
                } else {
                    Spinner<Integer> spinner = new Spinner<>();
                    spinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(-5, +5, item.getPoints()));
                    spinner.valueProperty().addListener((ChangeListener<Integer>) (observable, oldValue, newValue) -> {
                        item.pointsProperty().set(newValue);
                        System.out.println(item.pointsProperty().get());
                    });
                    setGraphic(spinner);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                }
            }
        });
    }
}

Once again: The Controller is the one, that is doing the binding. With Lambdas, the code is almost readable, but your should consider reworking it anyway.

Cheers, Daniel

Upvotes: 3

Related Questions