After submitting my implementation of a searchable JComboBox
, I discovered that what the reporter actually wanted was quite different. They want the combo box field to stay non-editable but also to see typed symbols. The only way to achieve it is to introduce a separate text field in the dropdown menu (it’s how I see it, anyways)
I did it, but it doesn’t work. Specifically, the text field, while technically editable (as confirmed through the debugger), does not allow any characters. I don’t even see a caret
package di;
import org.apache.commons.lang3.StringUtils;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
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);
JTextField searchTextField = new JTextField();
comboBox.setUI(searchableComboBoxUi(searchTextField));
searchTextField.getDocument().addDocumentListener(searchableComboBoxDocumentListener(searchTextField, comboBox, model));
return comboBox;
}
private static <T> DocumentListener searchableComboBoxDocumentListener(JTextField searchTextField, JComboBox<T> comboBox, ComboBoxModel<T> model) {
return new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
updateComboBoxModel();
}
@Override
public void removeUpdate(DocumentEvent e) {
updateComboBoxModel();
}
private void updateComboBoxModel() {
String enteredText = searchTextField.getText();
ArrayList<T> matchingElements = findMatchingElements(enteredText);
DefaultComboBoxModel<T> modelWithOnlyMatchingElements = new DefaultComboBoxModel<>(new Vector<>(matchingElements));
comboBox.setModel(modelWithOnlyMatchingElements);
comboBox.showPopup();
}
private ArrayList<T> findMatchingElements(String enteredText) {
ArrayList<T> matchingElements = new ArrayList<>();
for (int i = 0; i < model.getSize(); i++) {
T modelElement = model.getElementAt(i);
if (StringUtils.containsIgnoreCase(modelElement.toString(), enteredText))
matchingElements.add(modelElement);
}
return matchingElements;
}
@Override
public void changedUpdate(DocumentEvent e) {
}
};
}
private static ComboBoxUI searchableComboBoxUi(JTextField searchTextField) {
return new BasicComboBoxUI() {
@Override
protected ComboPopup createPopup() {
return new BasicComboPopup(comboBox) {
@Override
protected void configurePopup() {
super.configurePopup();
add(searchTextField, 0);
}
@Override
public void show() {
super.show();
searchTextField.requestFocus();
}
};
}
};
}
}
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();
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;
}
}
searchable jcombobox
It doesn’t have to do with the `setFocusable(false)`
call in the configurePopup() super implementation as calling setFocusable(true) in the override doesn’t fix the problem
@Override
protected void configurePopup() {
super.configurePopup();
setFocusable(true);
add(searchTextField, 0);
}
How do I resolve this issue and make the popup’s search field really editable?