DejanLekic
DejanLekic

Reputation: 19797

What is the best way to trigger a combo-box cell editor by typing in a JTable cell?

In other words, I want JTable to drop-down a combo-box whenever user types in a cell that has a JComboBox (or any other JComboBox-based cell-editor) editor associated to it.

Upvotes: 1

Views: 1525

Answers (2)

sergey
sergey

Reputation: 1

    JTable table = new JTable(data, columns);
    table.putClientProperty("terminateEditOnFocusLost", true);
    JScrollPane scrollPane = new JScrollPane(table);
    final JXComboBox editorComboBox = new JXComboBox(array);
    editorComboBox.addAncestorListener(new AncestorListener() {
        public void ancestorAdded(AncestorEvent event) {
            //make sure combobox handles key events
            editorComboBox.requestFocusInWindow();
        }
        public void ancestorMoved(AncestorEvent event) {}
        public void ancestorRemoved(AncestorEvent event) {}
    });
    AutoCompleteDecorator.decorate(editorComboBox);
    TableColumn column = table.getColumnModel().getColumn(0);
    column.setCellEditor(new ComboBoxCellEditor(editorComboBox));

Upvotes: 0

kleopatra
kleopatra

Reputation: 51525

Basically, you have to install an appropriate listener on the combo and open the popup explicitly. First candidate for "appropriate" is an AncestorListener, which invokes showing the popup in its ancestorAdded method.

Unfortunately that doesn't seem to be the whole story: works if the table's surrenderFocus property is false. If it is true works only for not-editable combos. After some digging, the reason for the not-working part turns out to be an internal focustransfer (from the combo to the textfield) after the popup is opened by the ancestorListener. In that case, we need a second listener which opens the popup once the editor's editingComponent got the focus permanently.

Multiple listeners routinely step onto each other's feet, so best to not install both permanently but do it on each call to getEditorComp, and let them uninstall themselves once they showed the popup. Below is a working example of how-to do it, just beware: it's not formally tested!

public static class DefaultCellEditorX extends DefaultCellEditor {
    private AncestorListener ancestorListener;
    private PropertyChangeListener focusPropertyListener;

    public DefaultCellEditorX(JComboBox comboBox) {
        super(comboBox);
    }

    /** 
     * Overridden to install an appriate listener which opens the
     * popup when actually starting an edit.
     * 
     * @inherited <p>
     */
    @Override
    public Component getTableCellEditorComponent(JTable table,
            Object value, boolean isSelected, int row, int column) {
        super.getTableCellEditorComponent(table, value, isSelected, row, column);
        installListener(table);
        return getComponent();
    }

    /**
     * Shows popup.
     */
    protected void showPopup() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                getComponent().setPopupVisible(true);
            }
        });
    }


    /**
     * Dynamically install self-uninstalling listener, depending on JComboBox
     * and JTable state. 
     * @param table
     */
    private void installListener(JTable table) {
        if (getComponent().isEditable() && table.getSurrendersFocusOnKeystroke()) {
            installKeyboardFocusListener();
        } else {
            installAncestorListener();
        }
    }

    private void installAncestorListener() {
        if (ancestorListener == null) {
            ancestorListener = new AncestorListener() {

                @Override
                public void ancestorAdded(AncestorEvent event) {
                    getComponent().removeAncestorListener(ancestorListener);
                    showPopup();
                }

                @Override
                public void ancestorRemoved(AncestorEvent event) {
                }

                @Override
                public void ancestorMoved(AncestorEvent event) {
                }

            };
        }
        getComponent().addAncestorListener(ancestorListener);
    }

    private void installKeyboardFocusListener() {
        if (focusPropertyListener == null) {
            focusPropertyListener = new PropertyChangeListener() {

                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    LOG.info("property: " + evt.getPropertyName());
                    if (focusManager().getPermanentFocusOwner() != 
                        getComponent().getEditor().getEditorComponent()) return;
                    focusManager()
                       .removePropertyChangeListener("permanentFocusOwner", focusPropertyListener);
                    showPopup();
                }

            };
        }
        focusManager().addPropertyChangeListener("permanentFocusOwner", focusPropertyListener);
    }

    /**
     * Convience for less typing.
     * @return
     */
    protected KeyboardFocusManager focusManager() {
        return KeyboardFocusManager.getCurrentKeyboardFocusManager();
    }

    /** 
     * Convenience for type cast.
     * @inherited <p>
     */
    @Override
    public JComboBox getComponent() {
        return (JComboBox) super.getComponent();
    }

}

Upvotes: 1

Related Questions