How to add keyboard edit support to ComboBoxTableCell

msebas :

ComboBoxTableCell allows to add a ComboBox to a TableCell in edit mode. If comboBox.show() was called (e.g. the popup is showing) the comboBox reacts on arrow-down and arrow-up keys pressed as expected and ends the edit mode as soon as enter is hit. I want to control the editing using only the keyboard. I was unable to find a way to get 'comboBox.show()` called using the keyboard.

Until now I tried to use setOnKeyPressed to add a callback to the ComboBoxTableCell (during creation by the factory method) or the ComboBox (by using ComboBoxTableCell.getGraphic()). The callback called ComboBox.show() to open the popup, but they were not called (verified by prints to System.out in the callback).

actColumn.setCellFactory(
        new Callback<TableColumn<S,Object>, TableCell<S,Object>>() {
    private ObservableList<Object> list=optionList;

    @SuppressWarnings("unchecked")
    @Override
    public TableCell<S, Object> call(TableColumn<S, Object> param) {
        final ComboBoxTableCell<S,Object> cell=
                new ComboBoxTableCell<S,Object>(list);
        cell.setConverter((StringConverter<Object>) converter);
        cell.setOnKeyPressed(event -> {
            cell.startEdit();

            Node node=cell.getGraphic();
            System.out.println(node);
            if(node instanceof ComboBox) {
                System.out.println("Hit Key.");
                final ComboBox<?> box=(ComboBox<?>) node;
                box.show();
            }
        });
        //We have to forcefully fill the combobox member and set the
        //graphic, because the cell does not init the ComboBox in 
                    //its constructor
        Platform.runLater(new Runnable() {
            @Override public void run() {
                cell.startEdit();

                Node node=cell.getGraphic();
                if(node instanceof ComboBox) {
                    ComboBox<?> box=(ComboBox<?>) node;
                    //Now we should have the combobox for this cell
                    box.setOnKeyPressed(event -> {
                        System.out.println("Hit Key.");
                        if(event.getCode()==KeyCode.DOWN) {
                            System.out.println("Hit Arrow.");
                            box.show();
                        }
                    });
                }

                //Stop editing again
                cell.cancelEdit();
             }
        });

        return cell;
    }
});

Beside this code being quite strange the handlers are not called when a key is hit in edit mode on the cell (or at least I get no output).

I want to be able to select a ComboBoxTableCell, hit enter (and possible one additional key) and then the popup of the internal ComboBox should show up without any interaction done by the mouse.

Slaw :

You can subclass ComboBoxTableCell to add the behavior you want. Here's a proof-of-concept:

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;

public class AutoShowComboBoxTableCell<S, T> extends ComboBoxTableCell<S, T> {

    /* 
     * May want to provide alternate constructors and static methods similar to
     * the ComboBoxTableCell class (i.e. the superclass).
     */

    private boolean enterPressed;

    public AutoShowComboBoxTableCell(StringConverter<T> converter, ObservableList<T> values) {
        super(converter, values);
        getStyleClass().add("auto-show-combo-box-table-cell");

        // Assumes TableView property is set only once (valid assumption?)
        tableViewProperty().addListener(new InvalidationListener() {
            @Override
            public void invalidated(Observable observable) {
                // Need to know if editing was started by the user pressing
                // the ENTER key (see #startEdit())
                EventHandler<KeyEvent> filter = event -> {
                    if (event.getCode() == KeyCode.ENTER) {
                        enterPressed = event.getEventType() == KeyEvent.KEY_PRESSED;
                    }
                };
                // Possible memory leak? Consider using WeakEventHandler (read docs)
                getTableView().addEventFilter(KeyEvent.KEY_PRESSED, filter);
                getTableView().addEventFilter(KeyEvent.KEY_RELEASED, filter);
                observable.removeListener(this);
            }
        });
    }

    @Override
    public void startEdit() {
        if (isEditing()) return;

        super.startEdit();
        if (isEditing()) {
            if (enterPressed) {
                // Cell was put into edit mode by the user pressing ENTER. This causes
                // problems since *releasing* ENTER while the ComboBox has the focus
                // results in the value being committed; this leads to the current value
                // being committed *immediately* after entering edit mode—not what we want. 
                // To fix that we consume the first ENTER-released event and then let all
                // subsequent events through (by removing the event filter).
                addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<>() {
                    @Override public void handle(KeyEvent event) {
                        if (event.getCode() == KeyCode.ENTER) {
                            event.consume();
                            removeEventFilter(KeyEvent.KEY_RELEASED, this);
                        }
                    }
                });
            }
            ComboBox<?> comboBox = (ComboBox<?>) getGraphic();
            comboBox.requestFocus(); // Needed to allow releasing ENTER to commit the value
            comboBox.show();
        }
    }

    @Override
    public void cancelEdit() {
        if (isEditing()) {
            super.cancelEdit();
            requestTableViewFocus();
        }
    }

    @Override
    public void commitEdit(T newValue) {
        if (isEditing()) {
            super.commitEdit(newValue);
            requestTableViewFocus();
        }
    }

    // Allows user to keep navigating the table via the keyboard
    private void requestTableViewFocus() {
        TableView<S> tableView = getTableView();
        if (tableView != null) {
            tableView.requestFocus();
        }
    }

}

Note: The above uses knowledge of implementation details, both the fact the graphic is set to the ComboBox when the edit is started and regarding what causes the edit to be committed. Implementation details can change without notice.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=154289&siteId=1