ComboBox in a tableview cell in JavaFX

20,374

Solution 1

James_D's answer works well, but requires the user to click on the item to see the ComboBox. If you want to have ComboBoxes in a column, that are always shown, you have to use a custom cellFactory:

Example:

public class TableViewTest {

    ...

    private final StringProperty option = new SimpleStringProperty();

    public String getOption() {
        return option.get();
    }

    public void setOption(String value) {
        option.set(value);
    }

    public StringProperty optionProperty() {
        return option;
    }
    
}
    TableColumn<TableViewTest, StringProperty> column = new TableColumn<>("option");
    column.setCellValueFactory(i -> {
        final StringProperty value = i.getValue().optionProperty();
        // binding to constant value
        return Bindings.createObjectBinding(() -> value);
    });
    
    column.setCellFactory(col -> {
        TableCell<TableViewTest, StringProperty> c = new TableCell<>();
        final ComboBox<String> comboBox = new ComboBox<>(options);
        c.itemProperty().addListener((observable, oldValue, newValue) -> {
            if (oldValue != null) {
                comboBox.valueProperty().unbindBidirectional(oldValue);
            }
            if (newValue != null) {
                comboBox.valueProperty().bindBidirectional(newValue);
            }
        });
        c.graphicProperty().bind(Bindings.when(c.emptyProperty()).then((Node) null).otherwise(comboBox));
        return c;
    });

Solution 2

The types for your table column are always the type of the item in each row (i.e. the same as the type you use for the table view) for the first type parameter, and the type of the (current) value for each cell in the column for the second parameter. So if your table view has type TableViewTest, and your combo box is selecting Strings, you should have:

TableColumn<TableViewTest, String> priceColumn ;

The cell value factory should still map to a property in the TableViewTest class, i.e. assuming you have:

public class TableViewTest {

    // ...

    public StringProperty priceProperty() {
        // ...
    }

    // ...
}

then you can do:

priceColumn.setCellValueFactory(new PropertyValueFactory<>("price"));

or (much better):

priceColumn.setCellValueFactory(cellData -> cellData.getValue().priceProperty());

Then you can just do:

priceColumn.setCellFactory(ComboBoxTableCell.forTableColumn(options));

Here's a SSCCE:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

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, String> categoryCol = new TableColumn<>("Category");
        categoryCol.setCellValueFactory(cellData -> cellData.getValue().categoryProperty());

        categoryCol.setCellFactory(ComboBoxTableCell.forTableColumn("Friends", "Family", "Work Contacts"));

        contactTable.getColumns().add(categoryCol);

        contactTable.getItems().addAll(
            new Contact("Bill Gates", "Work Contacts"),
            new Contact("Barack Obama", "Friends"),
            new Contact("Tim Cook", "Work Contacts")
        );

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

    public static class Contact {
        private final StringProperty name = new SimpleStringProperty();
        private final StringProperty category = new SimpleStringProperty();

        public Contact(String name, String category) {
            setName(name);
            setCategory(category);
        }

        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 StringProperty categoryProperty() {
            return this.category;
        }


        public final String getCategory() {
            return this.categoryProperty().get();
        }


        public final void setCategory(final String category) {
            this.categoryProperty().set(category);
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}
Share:
20,374
noobCoder
Author by

noobCoder

Updated on July 09, 2022

Comments

  • noobCoder
    noobCoder almost 2 years

    I'm trying to a add a Combo Box to my Table View:

    Basically I have a class called TableViewTest that stores a name and a description, I can display theses names and descriptions in a Table View no bother, but what I want to do is add a third column with each cell having a Combo Box so that the user can select one from a number of options for each person.

    So far I have created an ObservableList of type String with some values and added them to a ComboBox object. Does anyone know a way for me to add this Combo Box to the table?

    Also bear in mind this code is pretty rough and I'm just trying to get something working and I'll be refactoring the code at a later date.

    ObservableList<TableViewTest> products = FXCollections.observableArrayList();
    
        for(int i = 0; i < b.length; i++){
    
            // random String values
            products.add(new TableViewTest(b[i], a[i]));
        }
    
    ObservableList<String> options = FXCollections.observableArrayList(
                                    "1",
                                    "2",
                                    "3"
                                    );
    final ComboBox comboBox = new ComboBox(options);
    
    TableColumn<TableViewTest, String> nameColumn = new TableColumn<> ("Name");
    nameColumn.setMinWidth(200);
    nameColumn.setCellValueFactory(new PropertyValueFactory<TableViewTest, String>("name"));
    
                    //price Column
                    //Stock Column
    TableColumn<TableViewTest, String> StockColumn = new TableColumn<> ("Stock");
    StockColumn.setMinWidth(150);
    StockColumn.setCellValueFactory(new PropertyValueFactory<TableViewTest, String>("description"));
    
    
    TableColumn<Object,ComboBox> PriceColumn;
    PriceColumn = new TableColumn<>("Source");
    PriceColumn.setMinWidth(150);
       //PriceColumn.setCellValueFactory(new PropertyValueFactory<>
       //(options));
    
       //PriceColumn.setCellFactory(ComboBoxTableCell.forTableColumn(new 
       //DefaultStringConverter(), options));
    
    
       //PriceColumn.setCellFactory(ComboBoxTableCell.forTableColumn( 
       //comboBox));
    
    TableView<TableViewTest> table = new TableView<>();
    
    table.setItems(products);
    table.getColumns().addAll(nameColumn, StockColumn, PriceColumn);