Search code examples
swingautocompletejtablejcomboboxswingx

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.


Solution

  • 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();
        }
    
    }