[Solved] Java 8 method references with javafx


If you just want to define a method for creating a MenuItem, then it’s easy enough: you just need to decide on the functional interface you will need for the parameter that takes the method reference (or lambda, etc). E.g. if the method signature takes no parameters and has void return type, you could use Runnable:

public MenuItem createItem(String text, Runnable handler) {
    MenuItem item = new MenuItem(text);
    item.setOnAction(e -> handler.run());
}

You probably want the menu item event handler to have access to the table item in the row, in which case it would need a reference to the row:

public <T> MenuItem createItem(String text, TableRow<T> row, Consumer<T> handler) {
    MenuItem item = new MenuItem(text);
    item.setOnAction(e -> handler.accept(row.getItem()));
}

Then you can do

TableView<InterfaceModel> table = new TableView<>();
ContextMenuHelper helper = new ContextMenuHelper();
table.setRowFactory(t -> {
    TableRow<InterfaceModel> row = new TableRow<>();
    ContextMenu menu = new ContextMenu();
    row.setContextMenu(menu);
    menu.getItems().addItem(helper.createItem("Edit", row, this::edit));
    // etc...
});

with

private void edit(InterfaceModel model) {
    // ...
}

What you didn’t actually ask, but I’m sort of guessing you really want, is for the “helper” class to actually set the row factory and create all the menus, etc. This is a bit harder to structure, because you need to entirely build the context menu inside the row factory, so you need to know all the menu items before you can actually set the row factory. For this, you probably want to consider a builder pattern:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.util.Callback;

public class TableRowContextMenuBuilder<T> {
    private final List<MenuItemConfig<T>> items ;
    private boolean built ;

    public TableRowContextMenuBuilder() {
        this.items = new ArrayList<>();
    }


    public static <T> TableRowContextMenuBuilder<T> create(Class<T> type) {
        return new TableRowContextMenuBuilder<>();
    }

    public TableRowContextMenuBuilder<T> addItem(String text, Consumer<T> handler) {
        if (built) {
            throw new IllegalStateException("Row factory is already built: cannot add new items");
        }
        items.add(new MenuItemConfig<T>(text, handler));
        return this ;
    }

    public TableRowContextMenuBuilder<T> addItem(String text, Runnable handler) {
        return addItem(text, t -> handler.run());
    }

    public Callback<TableView<T>, TableRow<T>> build() {
        if (built) {
            throw new IllegalStateException("Cannot build row factory more than once");
        }
        built = true ;
        return t -> {
            TableRow<T> row = new TableRow<>();
            ContextMenu menu = new ContextMenu();
            row.setContextMenu(menu);
            items.stream()
                .map(config -> config.asMenuItem(row))
                .forEach(menu.getItems()::add);
            return row ;
        };
    }

    public void buildForTable(TableView<T> table) {
        table.setRowFactory(build());
    }


    private static class MenuItemConfig<T> {
        private final String text ;
        private final Consumer<T> handler ;
        MenuItemConfig(String text, Consumer<T> handler) {
            this.text = text;
            this.handler = handler;
        }
        MenuItem asMenuItem(TableRow<T> row) {
            MenuItem item = new MenuItem(text);
            item.setOnAction(e -> handler.accept(row.getItem()));
            return item ;
        }
    }
}

And now you can do

TableView<InterfaceModel> table = new TableView<>();
TableViewContextMenuBuilder.create(InterfaceModel.class)
    .menuBuilder.addItem("Edit", this::edit);
    .menuBuilder.addItem("Item 2", this::handleOtherItem);
    // ...
    .buildForTable(table);

with the appropriate methods defined:

private void edit(InterfaceModel model) { /* ... */}
private void handleOtherItem(InterfaceModel model) { /* ... */}

1

solved Java 8 method references with javafx