Java Swing - JCombBox Filtering and Highlighting With Custom Popup Component Example

[Updated: Dec 4, 2017, Created: Dec 4, 2017]

In the last example, we implemented filtering functionality for JComboBox, there we had to make JComboBox editable, but in fact the purpose was not editing but filtering. Also it might not be always possible, for any reasons, to enable editing flag for the JComboBox. In following example, we will implement filtering without making the combo box editable. We are going to create a custom popup component to show the filtering text. This will also preserve the default functionality of the JComboBox of showing selection changes in the main text component while navigating the drop down list.

Main class

public class JComboBoxFilterExample {

  public static void main(String[] args) {
      List<Employee> employees = EmployeeDataAccess.getEmployees();
      JComboBox<Employee> comboBox = new JComboBox<>(
              employees.toArray(new Employee[employees.size()]));
      ComboBoxFilterDecorator<Employee> decorate = ComboBoxFilterDecorator.decorate(comboBox,
              JComboBoxFilterExample::employeeFilter);
      comboBox.setRenderer(new CustomComboRenderer(decorate.getFilterLabel()));

      JPanel panel = new JPanel();
      panel.add(comboBox);

      JFrame frame = createFrame();
      frame.add(panel);
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
  }
    .............
}

The Filtering decorator

public class ComboBoxFilterDecorator<T> {
    private Popup filterPopup;
    private JLabel filterLabel;
    private JComboBox<T> comboBox;
    private BiPredicate<T, String> userFilter;
    java.util.List<T> items;
    private TextHandler textHandler = new TextHandler();
    private Object selectedItem;

    public ComboBoxFilterDecorator(JComboBox<T> comboBox, BiPredicate<T, String> userFilter) {
        this.comboBox = comboBox;
        this.userFilter = userFilter;
    }

    public static <T> ComboBoxFilterDecorator<T> decorate(JComboBox<T> comboBox,
                                                          BiPredicate<T, String> userFilter) {
        ComboBoxFilterDecorator decorator = new ComboBoxFilterDecorator(comboBox, userFilter);
        decorator.init();
        return decorator;
    }

    private void init() {
        prepareComboFiltering();
        initFilterLabel();
        initComboPopupListener();
        initComboKeyListener();
    }

    private void prepareComboFiltering() {
        DefaultComboBoxModel<T> model = (DefaultComboBoxModel<T>) comboBox.getModel();
        items = new ArrayList<>();
        for (int i = 0; i < model.getSize(); i++) {
            items.add(model.getElementAt(i));
        }
    }

    private void initComboKeyListener() {
        comboBox.addKeyListener(
                new KeyAdapter() {
                    @Override
                    public void keyPressed(KeyEvent e) {
                        char keyChar = e.getKeyChar();
                        if (!Character.isDefined(keyChar)) {
                            return;
                        }
                        int keyCode = e.getKeyCode();
                        switch (keyCode) {
                            case KeyEvent.VK_DELETE:
                                return;
                            case KeyEvent.VK_ENTER:
                                resetFilterPopup();
                                return;
                            case KeyEvent.VK_ESCAPE:
                                if (selectedItem != null) {
                                    comboBox.setSelectedItem(selectedItem);
                                }
                                resetFilterPopup();
                                return;
                            case KeyEvent.VK_BACK_SPACE:
                                textHandler.removeCharAtEnd();
                                break;
                            default:
                                textHandler.add(keyChar);
                        }

                        if (!comboBox.isPopupVisible()) {
                            comboBox.showPopup();
                        }

                        if (!textHandler.text.isEmpty()) {
                            showFilterPopup();
                            performFilter();
                        } else {
                            resetFilterPopup();
                        }
                        e.consume();
                    }
                }
        );
    }

    private void initFilterLabel() {
        filterLabel = new JLabel();
        filterLabel.setOpaque(true);
        filterLabel.setBackground(new Color(255, 248, 220));
        filterLabel.setFont(filterLabel.getFont().deriveFont(Font.PLAIN));
        filterLabel.setBorder(BorderFactory.createLineBorder(Color.gray));
    }

    public JLabel getFilterLabel() {
        return filterLabel;
    }

    private void initComboPopupListener() {
        comboBox.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                resetFilterPopup();
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                resetFilterPopup();
            }
        });
    }

    private void showFilterPopup() {
        if (textHandler.getText().isEmpty()) {
            return;
        }
        if (filterPopup == null) {
            Point p = new Point(0, 0);
            SwingUtilities.convertPointToScreen(p, comboBox);
            Dimension comboSize = comboBox.getPreferredSize();
            filterLabel.setPreferredSize(new Dimension(comboSize));
            filterPopup = PopupFactory.getSharedInstance().getPopup(comboBox, filterLabel, p.x,
                    p.y - filterLabel.getPreferredSize().height);
            selectedItem = comboBox.getSelectedItem();

        }
        filterPopup.show();
    }

    private void resetFilterPopup() {
        if (!textHandler.isEditing()) {
            return;
        }
        if (filterPopup != null) {
            filterPopup.hide();
            filterPopup = null;
            filterLabel.setText("");
            textHandler.reset();

            //add items in the original order
            Object selectedItem = comboBox.getSelectedItem();
            DefaultComboBoxModel<T> model = (DefaultComboBoxModel<T>) comboBox.getModel();
            model.removeAllElements();
            for (T item : items) {
                model.addElement(item);
            }
            //preserve the selection
            model.setSelectedItem(selectedItem);
            this.selectedItem = selectedItem;
        }
    }

    private void performFilter() {
        filterLabel.setText(textHandler.getText());
        //  System.out.println("'" + textHandler.getText() + "'");
        DefaultComboBoxModel<T> model = (DefaultComboBoxModel<T>) comboBox.getModel();
        model.removeAllElements();
        java.util.List<T> filteredItems = new ArrayList<>();
        //add matched items first
        for (T item : items) {
            if (userFilter.test(item, textHandler.getText())) {
                model.addElement(item);
            } else {
                filteredItems.add(item);
            }
        }
        if (model.getSize() == 0) {
            //if no match then red font
            filterLabel.setForeground(Color.red);
        } else {
            filterLabel.setForeground(Color.blue);
        }
        //add unmatched items
        filteredItems.forEach(model::addElement);
    }

    private static class TextHandler {
        private String text = "";
        private boolean editing;

        public void add(char c) {
            text += c;
            editing = true;
        }

        public void removeCharAtEnd() {
            if (text.length() > 0) {
                text = text.substring(0, text.length() - 1);
                editing = true;
            }
        }

        public void reset() {
            text = "";
            editing = false;
        }

        public String getText() {
            return text;
        }

        public boolean isEditing() {
            return editing;
        }
    }
}

Output

Example Project

Dependencies and Technologies Used :

  • datafactory 0.8: Library to generate data for testing.
  • JDK 1.8
  • Maven 3.3.9

JCombBox Filtering With Custom Popup Component Example Select All Download
  • combo-box-popup-filter
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also