This example implements filtering and highlighting functionality for JComboBox component. We are going to create a custom ComboBoxEditor having JLabel as an editor component. Like other examples it also uses a custom renderer with Html highlighting as well. The filtering is based on KeyListener , so if the combo box is in focus, pressing keys will start the filtering. Also to maintain the default JComboBox up/down arrow key navigation/selection functionality, this example does not remove the unmatched items, instead it appends them to the end of the list. After selection, the original order of the items is restored. The selection can be made by hitting enter key or mouse click. Backspace key will remove the characters from end. Escape key will rollback the search.
Main class
public class JComboBoxFilterMain {
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,
CustomComboRenderer::getEmployeeDisplayText,
JComboBoxFilterMain::employeeFilter);
comboBox.setRenderer(new CustomComboRenderer(decorate.getFilterTextSupplier()));
JPanel panel = new JPanel();
panel.add(comboBox);
JFrame frame = createFrame();
frame.add(panel);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static boolean employeeFilter(Employee emp, String textToFilter) {
if (textToFilter.isEmpty()) {
return true;
}
return CustomComboRenderer.getEmployeeDisplayText(emp).toLowerCase()
.contains(textToFilter.toLowerCase());
}
private static JFrame createFrame() {
JFrame frame = new JFrame("JComboBox Filter Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(600, 300));
return frame;
}
}
The Filtering decorator
public class ComboBoxFilterDecorator<T> {
private JComboBox<T> comboBox;
private BiPredicate<T, String> userFilter;
private Function<T, String> comboDisplayTextMapper;
java.util.List<T> originalItems;
private Object selectedItem;
private FilterEditor filterEditor;
public ComboBoxFilterDecorator(JComboBox<T> comboBox,
BiPredicate<T, String> userFilter,
Function<T, String> comboDisplayTextMapper) {
this.comboBox = comboBox;
this.userFilter = userFilter;
this.comboDisplayTextMapper = comboDisplayTextMapper;
}
public static <T> ComboBoxFilterDecorator<T> decorate(JComboBox<T> comboBox,
Function<T, String> comboDisplayTextMapper,
BiPredicate<T, String> userFilter) {
ComboBoxFilterDecorator decorator =
new ComboBoxFilterDecorator(comboBox, userFilter, comboDisplayTextMapper);
decorator.init();
return decorator;
}
private void init() {
prepareComboFiltering();
initComboPopupListener();
initComboKeyListener();
}
private void prepareComboFiltering() {
DefaultComboBoxModel<T> model = (DefaultComboBoxModel<T>) comboBox.getModel();
this.originalItems = new ArrayList<>();
for (int i = 0; i < model.getSize(); i++) {
this.originalItems.add(model.getElementAt(i));
}
filterEditor = new FilterEditor(comboDisplayTextMapper, new Consumer<Boolean>() {
//editing mode (commit/cancel) change listener
@Override
public void accept(Boolean aBoolean) {
if (aBoolean) {//commit
selectedItem = comboBox.getSelectedItem();
} else {//rollback to the last one
comboBox.setSelectedItem(selectedItem);
filterEditor.setItem(selectedItem);
}
}
});
JLabel filterLabel = filterEditor.getFilterLabel();
filterLabel.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
filterLabel.setBorder(BorderFactory.createLoweredBevelBorder());
}
@Override
public void focusLost(FocusEvent e) {
filterLabel.setBorder(UIManager.getBorder("TextField.border"));
resetFilterComponent();
}
});
comboBox.setEditor(filterEditor);
comboBox.setEditable(true);
}
private void initComboKeyListener() {
filterEditor.getFilterLabel().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:
selectedItem = comboBox.getSelectedItem();
resetFilterComponent();
return;
case KeyEvent.VK_ESCAPE:
resetFilterComponent();
return;
case KeyEvent.VK_BACK_SPACE:
filterEditor.removeCharAtEnd();
break;
default:
filterEditor.addChar(keyChar);
}
if (!comboBox.isPopupVisible()) {
comboBox.showPopup();
}
if (filterEditor.isEditing() && filterEditor.getText().length() > 0) {
applyFilter();
} else {
comboBox.hidePopup();
resetFilterComponent();
}
}
}
);
}
public Supplier<String> getFilterTextSupplier() {
return () -> {
if (filterEditor.isEditing()) {
return filterEditor.getFilterLabel().getText();
}
return "";
};
}
private void initComboPopupListener() {
comboBox.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
resetFilterComponent();
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
resetFilterComponent();
}
});
}
private void resetFilterComponent() {
if (!filterEditor.isEditing()) {
return;
}
//restore original order
DefaultComboBoxModel<T> model = (DefaultComboBoxModel<T>) comboBox.getModel();
model.removeAllElements();
for (T item : originalItems) {
model.addElement(item);
}
filterEditor.reset();
}
private void applyFilter() {
DefaultComboBoxModel<T> model = (DefaultComboBoxModel<T>) comboBox.getModel();
model.removeAllElements();
java.util.List<T> filteredItems = new ArrayList<>();
//add matched items at top
for (T item : originalItems) {
if (userFilter.test(item, filterEditor.getFilterLabel().getText())) {
model.addElement(item);
} else {
filteredItems.add(item);
}
}
//red color when no match
filterEditor.getFilterLabel()
.setForeground(model.getSize() == 0 ?
Color.red : UIManager.getColor("Label.foreground"));
//add unmatched items
filteredItems.forEach(model::addElement);
}
}
A custom ComboBoxEditor implementation
public class FilterEditor<T> extends BasicComboBoxEditor {
private JLabel filterLabel = new JLabel();
private String text = "";
boolean editing;
private Function<T, String> displayTextFunction;
private Consumer<Boolean> editingChangeListener;
private Object selected;
FilterEditor(Function<T, String> displayTextFunction,
Consumer<Boolean> editingChangeListener) {
this.displayTextFunction = displayTextFunction;
this.editingChangeListener = editingChangeListener;
}
public void addChar(char c) {
text += c;
if (!editing) {
enableEditingMode();
}
}
public void removeCharAtEnd() {
if (text.length() > 0) {
text = text.substring(0, text.length() - 1);
if (!editing) {
enableEditingMode();
}
}
}
private void enableEditingMode() {
editing = true;
filterLabel.setFont(filterLabel.getFont().deriveFont(Font.PLAIN));
editingChangeListener.accept(true);
}
public void reset() {
if (editing) {
filterLabel.setFont(UIManager.getFont("ComboBox.font"));
filterLabel.setForeground(UIManager.getColor("Label.foreground"));
text = "";
editing = false;
editingChangeListener.accept(false);
}
}
@Override
public Component getEditorComponent() {
return filterLabel;
}
public JLabel getFilterLabel() {
return filterLabel;
}
@Override
public void setItem(Object anObject) {
if (editing) {
filterLabel.setText(text);
} else {
T t = (T) anObject;
filterLabel.setText(displayTextFunction.apply(t));
}
this.selected = anObject;
}
@Override
public Object getItem() {
return selected;
}
@Override
public void selectAll() {
}
@Override
public void addActionListener(ActionListener l) {
}
@Override
public void removeActionListener(ActionListener l) {
}
public boolean isEditing() {
return editing;
}
public String getText() {
return text;
}
}
Output
Example ProjectDependencies and Technologies Used: - datafactory 0.8: Library to generate data for testing.
- JDK 1.8
- Maven 3.3.9
|
|