JComboBox с возможностью поиска и пользовательскими элементами

Мне нужно реализовать функцию поиска JComboBox: которая фильтрует отображаемые элементы по мере ввода.

Вдохновленный этим ответом, я написал эту реализацию.

package di;

import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;

public class ComboBoxes {
    private ComboBoxes() {
    }

    public static <T> JComboBox<T> searchableComboBox() {
        return searchableComboBox(new ArrayList<>());
    }

    public static <T> JComboBox<T> searchableComboBox(Collection<T> items) {
        ComboBoxModel<T> model = new DefaultComboBoxModel<>(new Vector<>(items));
        JComboBox<T> comboBox = new JComboBox<>(model);
        comboBox.setEditable(true);
        ComboBoxEditor comboBoxEditor = comboBox.getEditor();
        JTextField editorTextField = (JTextField) comboBoxEditor.getEditorComponent();
        KeyAdapter searchableComboBoxKeyListener = createSearchableComboBoxKeyListener(comboBox, model);
        editorTextField.addKeyListener(searchableComboBoxKeyListener);
        return comboBox;
    }

    private static <T> KeyAdapter createSearchableComboBoxKeyListener(JComboBox<T> comboBox, ComboBoxModel<T> initialModel) {
        return new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                JTextField editorTextField = (JTextField) comboBox.getEditor().getEditorComponent();
                String enteredText = editorTextField.getText();
                ArrayList<T> matchingElements = findMatchingElements(enteredText);
                DefaultComboBoxModel<T> modelWithOnlyMatchingElements = new DefaultComboBoxModel<>(new Vector<>(matchingElements));
                comboBox.setModel(modelWithOnlyMatchingElements);
                comboBox.setSelectedItem(enteredText);
                comboBox.showPopup();
            }

            private ArrayList<T> findMatchingElements(String enteredText) {
                ArrayList<T> matchingElements = new ArrayList<>();
                for (int i = 0; i < initialModel.getSize(); i++) {
                    T modelElement = initialModel.getElementAt(i);
                    if (StringUtils.containsIgnoreCase(modelElement.toString(), enteredText))
                        matchingElements.add(modelElement);
                }
                return matchingElements;
            }
        };
    }
}

Он отлично работает со строками. С пользовательскими объектами все работает нормально, но есть небольшая проблема.

Ключевой прослушиватель устанавливает введенную строку как новый выбранный элемент. Это может сойти с рук, поскольку setSelectedItem() принимает Object, хотя JComboBox является параметризованным типом (поскольку дженерики были введены намного позже, чем Swing, JComboBox на самом деле не является универсальным типом)

Поэтому, если вы прослушиваете новые выборки и ожидаете, что выбранные элементы будут иметь аргумент типа JComboBox, вы получите ClassCastException, поскольку строки не могут быть приведены к этому типу. Вот демо

package demos.combo;

import di.ComboBoxes;

import javax.swing.*;
import java.awt.event.ItemEvent;
import java.util.ArrayList;

public class SearchableComboBoxDemo {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Searchable Combo Box Demo");
        JPanel mainPanel = new JPanel();
        JComboBox<Plant> searchableCombo = ComboBoxes.searchableComboBox();
        searchableCombo.addItemListener(SearchableComboBoxDemo::onItemSelection);
        comboBoxItems().forEach(searchableCombo::addItem);
        mainPanel.add(searchableCombo);
        frame.setContentPane(mainPanel);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static void onItemSelection(ItemEvent event) {
        if (event.getStateChange() != ItemEvent.SELECTED) return;
        Plant selectedItem = (Plant) event.getItem(); // throws
        System.out.printf("Selected item: " + selectedItem + "\n");
    }

    private static ArrayList<Plant> comboBoxItems() {
        ArrayList<Plant> items = new ArrayList<>();
        items.add(new Plant("Potato"));
        items.add(new Plant("Peach"));
        items.add(new Plant("Banana"));
        items.add(new Plant("Orange"));
        items.add(new Plant("Carrot"));
        items.add(new Plant("Cabbage"));
        return items;
    }
}
package demos.combo;

public class Plant {
    private final String name;

    public Plant(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

Звонок setSelectedItem() важен. Если вы удалите его, текстовое поле восстановит допустимое значение после того, как вы попытаетесь удалить символ. Это побочный эффект установки новой модели

Можно было бы предположить, что установка текста текстового поля напрямую исправит это.

                comboBox.setModel(modelWithOnlyMatchingElements);
                editorTextField.setText(enteredText); // like this
                comboBox.showPopup();

но тогда JComboBox не срабатывает ItemEvent, как ожидалось. Попробуйте ввести «о», затем нажать «Оранжевый», затем выделить и удалить весь текст, затем выбрать «Картошка». Вы не получите событие «Картошка выбрана». Это потому, что когда поле очищается и устанавливается новая модель, выбранный элемент новой модели (в данном случае первый элемент) автоматически назначается полю JComboBoxselectedItemReminder.

/*
the invoked DefaultComboBoxModel constructor automatically selects the first element,
there's nothing we can do about it
*/
    public DefaultComboBoxModel(Vector<E> v) {
        objects = v;

        if ( getSize() > 0 ) {
            selectedObject = getElementAt( 0 );
        }
    }

Если выбор пользователя соответствует этому (а это так), то IventItem никогда не запускается (см. javax.swing.JComboBox#setSelectedItem).

Итак, вкратце, как мне реализовать возможность поиска JComboBox:

  1. ...с нестроковыми элементами
  2. ...что ItemListener может слушать

?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Один из обходных путей — установить selectedItem на null.

comboBox.setModel(modelWithOnlyMatchingElements);
comboBox.setSelectedItem(null);
editorTextField.setText(enteredText);
comboBox.showPopup();

Другие вопросы по теме