可搜索带有自定义项目的 JComboBox

问题描述 投票:0回答:1

我需要实现一个“可搜索”

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
。尝试输入“o”,然后单击“橙色”,然后选择并删除所有文本,然后选择“土豆”。您不会收到“选择土豆”事件。这是因为当清除该字段并设置新模型时,新模型的选定项(本例中为第一项)会自动分配给
JComboBox
selectedItemReminder
字段

/*
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
    可以听

java swing
1个回答
0
投票

一种解决方法是将

selectedItem
设置为
null

                comboBox.setModel(modelWithOnlyMatchingElements);
                comboBox.setSelectedItem(null);
                editorTextField.setText(enteredText);
                comboBox.showPopup();
© www.soinside.com 2019 - 2024. All rights reserved.