Search code examples
comboboxtableviewjavafx-8fxml

JavaFX 8 How to pass and use a reference to a method that doesn't return a value?


I'm trying to pass and use a method reference to another method. The reference is to a setter in a TableView's model class that accepts a String input parameter and doesn't return a value.

In my FMXL controller, I'm dynamically creating table columns that contain Combo Boxes. The code is based on James_D's solution here: Java FX ComboBoxTableCell Show In Every Cell. I pass the method reference as follows:

TableColumn<TestModel, DBComboChoice> colComboBoxField = DAOGenUtil.createComboBoxColumn
        ("ComboBox Field",
        TestModel::comboBoxFieldProperty, //this one works
        TestModel::setComboBoxField, //this one doesn't work
        comboData);

I'm stuck in two places in the createComboBoxColumn method - in the method declaration and in the setOnAction.

public <S> TableColumn<S, DBComboChoice> createComboBoxColumn(String title, 
        Function<S, StringProperty> methodGetComboFieldProperty, 
//==>   <WHAT-GOES-HERE?> methodSetComboField,
        ObservableList<DBComboChoice> comboData ) {

    TableColumn<S, DBComboChoice> col = new TableColumn<>(title);

    col.setCellValueFactory(cellData -> {
        String masterCode =  methodGetComboFieldProperty.apply(cellData.getValue()).get();
        DBComboChoice choice = DBComboChoice.getDescriptionByMasterCode(masterCode, comboData);
        return new SimpleObjectProperty<>(choice);
    });

    col.setCellFactory((TableColumn<S, DBComboChoice> tablecol) -> {
        ComboBox<DBComboChoice> combo = new ComboBox<>();
        combo.getItems().addAll(comboData);
        TableCell<S, DBComboChoice> cell = new TableCell<S, DBComboChoice>() {
            @Override
            protected void updateItem(DBComboChoice choice, boolean empty) {
                super.updateItem(choice, empty);
                if (empty) {
                    setGraphic(null);
                } else {
                    combo.setValue(choice);
                    setGraphic(combo);
                }
            }
        };

        combo.setOnAction((ActionEvent event) -> {
            String masterCode =  combo.getSelectionModel().getSelectedItem().getMasterCode();
//==>       col.getTableView().getItems().get(cell.getIndex()).<AND-HOW-DO-I-USE-IT-TO-SET-THE-DATA-MODEL'S-FIELD-TO-masterCode?>
        });

        return cell ;

    });      

    return col;
}

When I tried Function<S, Void> methodSetComboField, I got an error in the FXML controller ("method createComboBoxColumn in class DAOGenUtil cannot be applied to given types"). BiConsumer<S, String> methodSetComboField didn't generate an error but I couldn't figure out how to use it in the setOnAction.

Can anyone help please? I'm using JavaFX8, NetBeans 8.2 and Scene Builder 8.3.

DBComboChoice is a class that contains a masterCode and a masterDescription eg. "F" for "Female", "M" for "Male". The masterCode is stored in the TableView's model. The masterDescription is shown in the ComboBox. I'm loading the values from a database master table.

Here are the relevant bits from the TableView's data model class:

public class TestModel {

//...

    public String getComboBoxField() {
        return comboBoxField.get();
    }

    public void setComboBoxField(String comboBoxField) {
        this.comboBoxField.set(comboBoxField);
    }

    public StringProperty comboBoxFieldProperty() {
        return comboBoxField;
    }


//...

}

Solution

  • Let's take a look at the signature available:

    void setComboBoxField(String)
    

    since you don't want to specify the instance but use TestModel::setComboBoxField, you need a functional interface with a method that accepts 2 parameters: TestModel and String. You could create such an interface yourself easily

    @FunctionalInterface
    public interface MyInterface<S, T> {
        void call(S s, T t);
    }
    
    public <S> TableColumn<S, DBComboChoice> createComboBoxColumn(String title, 
            Function<S, StringProperty> methodGetComboFieldProperty, 
            MyInterface<? super S, ? super String> methodSetComboField,
            ObservableList<DBComboChoice> comboData )
    

    Or use the existing interface BiConsumer:

    public <S> TableColumn<S, DBComboChoice> createComboBoxColumn(String title, 
            Function<S, StringProperty> methodGetComboFieldProperty, 
            BiConsumer<? super S, ? super String> methodSetComboField,
            ObservableList<DBComboChoice> comboData ) {
        ...
        methodSetComboField.accept(col.getTableView().getItems().get(cell.getIndex()), masterCode);
        ...
    }