-1

I have a table where few columns are of type combobox. Now I dnt want those list to be static when table is loaded rather.. if I select a value in cell1 based on which drop-down of cell2 should be populated. How can we achieve this?

  • 4
    Please read this and update your question to show some code: [How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) – Miss Chanandler Bong Sep 07 '21 at 07:04

1 Answers1

1

Generic (non-dependent) combobox selection in tables

This answer is based on modifications to James's answer to:

So you may refer to that for some more contextual info on the solution.

Description of this example

In the example, we have a first drop-down, a choice of relationships to a named person. And a second drop-down for a gift to provide to the named person.

The selection of available gifts depends on the relationship, the closer the relationship, the higher the value of the available gifts.

The image below shows choices available for selection when editing a value in the Relationship column. These choices are fixed and do not change:

first dropdown

The image below shows choices available for selection when editing a value in the Gift column, for a chosen relationship of FAMILY:

second dropdown option A

The image below shows choices available for selection when editing a value in the Gift column, for a chosen relationship of ACQUAINTANCE:

second dropdown option B

How it works

ComboBoxTableCell is used to generate cell factories for editing selections.

Enumerations are used to represent different selection types for relationships and gifts. But you could just use Strings rather than enumerations if you wanted a more dynamic setup with less static typing.

public enum GiftType {
    TOY, COFFEE, DINNER, VACATION
}

public enum RelationshipType {
    FAMILY, FRIEND, ACQUAINTANCE;
}

The selection of relationships, which just has a static selection list, uses a CombBoxTableCell without any modifications:

TableColumn<Contact, Contact.RelationshipType> relationshipCol = 
    new TableColumn<>("Relationship");
relationshipCol.setCellValueFactory(cellData ->
    cellData.getValue().relationshipProperty()
);
relationshipCol.setCellFactory(
    ComboBoxTableCell.forTableColumn(
          Contact.RelationshipType.values()
    )
);

The selection of gifts is dependent on the chosen relationship, so requires a bit more work to implement:

TableColumn<Contact, Contact.GiftType> giftCol = 
    new TableColumn<>("Gift");
giftCol.setCellValueFactory(cellData -> 
    cellData.getValue().giftProperty()
);
giftCol.setCellFactory(this::giftCellFactory);

Where the giftCellFactory is:

private TableCell<Contact, Contact.GiftType> giftCellFactory(
  TableColumn<Contact, Contact.GiftType> list
) {
    return new ComboBoxTableCell<>() {
        @Override
        public void startEdit() {
            getItems().setAll(
                    getTableRow().getItem().getAvailableGifts()
            );
            super.startEdit();
        }
    };
}

What the gift cell factory is doing is listening to the start of the editing event for a gift cell. When the editing starts, it updates the list of available gift values for selection to restrict them to just the available gifts that are available for the chosen relationship type of the item. That way, when the editing ComboBox appears it will only show valid selections for the given relationship.

The display of the relationships and gifts is just relying on the standard toString value of the enumerated types. However, if you wanted to customize the values (e.g. change case, or map a chosen code to a value), then you can supply a StringConverter to the constructor of the CombBoxTableCell.

To allow this functionality to work, in the model class (in this case a class called Contact), we provide a number of facilities.

  1. We represent all values as properties with getters/setters and property accessors.

  2. We provide a listener for the relationship property.

    In the listener, if the relationship changes and that would result in a gift value that would be invalid for that relationship type, then the current gift for that contact is set to null.

    relationshipProperty().addListener((observable, oldValue, newValue) -> {
        if (availableGifts.get(newValue) == null || !availableGifts.get(newValue).contains(getGift())) {
            setGift(null);
        }
    });
    
  3. We provide a mapping of relationships to available gift types.

    To allow the selection of no gift for a given contact, we place a single null value in each available gift selection list. If we wanted to require a gift, rather than making it optional, then we would not provide a null value for selection in the gift options list.

    For this mapping, we have accessors to allow:

    • retrieving all gifts for any given relationship type, and
    • the gifts which are available for the current relationship chosen for this contact.

    The mapping in this example is in the Contact model class, but if you wish, you could manage the mapping elsewhere (e.g. in a separate database relation).

    private static final Map<RelationshipType, List<GiftType>> availableGifts = Map.of(
            RelationshipType.FAMILY, Arrays.asList(null, GiftType.TOY, GiftType.DINNER, GiftType.VACATION),
            RelationshipType.FRIEND, Arrays.asList(null, GiftType.TOY, GiftType.COFFEE, GiftType.DINNER),
            RelationshipType.ACQUAINTANCE, Arrays.asList(null, GiftType.COFFEE)
    );
    
    public static List<GiftType> listAvailableGiftsForRelationshipType(RelationshipType relationshipType) {
        return availableGifts.get(relationshipType);
    }
    
    public List<GiftType> getAvailableGifts() {
        return availableGifts.get(getRelationship());
    }
    

Example Code

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import java.util.*;

public class TableWithComboBoxExample extends Application {
    @Override
    public void start(Stage primaryStage) {
        TableView<Contact> contactTable = new TableView<>();
        contactTable.setEditable(true);

        TableColumn<Contact, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
        nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
        contactTable.getColumns().add(nameCol);

        TableColumn<Contact, Contact.RelationshipType> relationshipCol = new TableColumn<>("Relationship");
        relationshipCol.setCellValueFactory(cellData -> cellData.getValue().relationshipProperty());
        relationshipCol.setCellFactory(ComboBoxTableCell.forTableColumn(Contact.RelationshipType.values()));
        relationshipCol.setPrefWidth(150);
        contactTable.getColumns().add(relationshipCol);

        TableColumn<Contact, Contact.GiftType> giftCol = new TableColumn<>("Gift");
        giftCol.setCellValueFactory(cellData -> cellData.getValue().giftProperty());
        giftCol.setCellFactory(this::giftCellFactory);
        giftCol.setPrefWidth(120);
        contactTable.getColumns().add(giftCol);

        contactTable.getItems().addAll(
                new Contact("Braxton Walls", Contact.RelationshipType.FAMILY, Contact.GiftType.VACATION),
                new Contact("Zainab Berger", Contact.RelationshipType.ACQUAINTANCE, null),
                new Contact("Safiyah Hail", Contact.RelationshipType.FRIEND, Contact.GiftType.DINNER),
                new Contact("Akbar Storey", Contact.RelationshipType.ACQUAINTANCE, null)
        );
        contactTable.setPrefSize(400, 200);

        Scene scene = new Scene(new BorderPane(contactTable));
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private TableCell<Contact, Contact.GiftType> giftCellFactory(TableColumn<Contact, Contact.GiftType> list) {
        return new ComboBoxTableCell<>() {
            @Override
            public void startEdit() {
                getItems().setAll(
                        getTableRow().getItem().getAvailableGifts()
                );
                super.startEdit();
            }
        };
    }

    public static class Contact {
        public enum GiftType {
            TOY, COFFEE, DINNER, VACATION
        }

        public enum RelationshipType {
            FAMILY, FRIEND, ACQUAINTANCE;
        }

        private static final Map<RelationshipType, List<GiftType>> availableGifts = Map.of(
                RelationshipType.FAMILY, Arrays.asList(null, GiftType.TOY, GiftType.DINNER, GiftType.VACATION),
                RelationshipType.FRIEND, Arrays.asList(null, GiftType.TOY, GiftType.COFFEE, GiftType.DINNER),
                RelationshipType.ACQUAINTANCE, Arrays.asList(null, GiftType.COFFEE)
        );

        private final StringProperty name = new SimpleStringProperty();
        private final ObjectProperty<RelationshipType> relationship = new SimpleObjectProperty<>();
        private final ObjectProperty<GiftType> gift = new SimpleObjectProperty<>();

        public Contact(String name, RelationshipType relationship, GiftType gift) {
            setName(name);
            setRelationship(relationship);
            relationshipProperty().addListener((observable, oldValue, newValue) -> {
                if (availableGifts.get(newValue) == null || !availableGifts.get(newValue).contains(getGift())) {
                    setGift(null);
                }
            });
            setGift(gift);
        }

        public final StringProperty nameProperty() {
            return this.name;
        }
        public final String getName() {
            return this.nameProperty().get();
        }
        public final void setName(final String name) {
            this.nameProperty().set(name);
        }

        public final ObjectProperty<RelationshipType> relationshipProperty() {
            return this.relationship;
        }
        public final RelationshipType getRelationship() {
            return this.relationshipProperty().get();
        }
        public final void setRelationship(final RelationshipType relationship) {
            this.relationshipProperty().set(relationship);
        }

        public final ObjectProperty<GiftType> giftProperty() {
            return this.gift;
        }
        public final GiftType getGift() {
            return this.giftProperty().get();
        }
        public final void setGift(final GiftType gift) {
            this.giftProperty().set(gift);
        }

        public static List<GiftType> listAvailableGiftsForRelationshipType(RelationshipType relationshipType) {
            return availableGifts.get(relationshipType);
        }

        public List<GiftType> getAvailableGifts() {
            return availableGifts.get(getRelationship());
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406