I want a ComboBox, that filteres the list items as the user types. It should work as follow:
Is there anything like that available?
As far as the filtering of the drop down is concerned. Isn't wrapping the list of possible options in a FilteredList
the best solution?
MCVE:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MCVE extends Application {
public void start(Stage stage) {
HBox root = new HBox();
ComboBox<String> cb = new ComboBox<String>();
cb.setEditable(true);
// Create a list with some dummy values.
ObservableList<String> items = FXCollections.observableArrayList("One", "Two", "Three", "Four", "Five", "Six",
"Seven", "Eight", "Nine", "Ten");
// Create a FilteredList wrapping the ObservableList.
FilteredList<String> filteredItems = new FilteredList<String>(items, p -> true);
// Add a listener to the textProperty of the combobox editor. The
// listener will simply filter the list every time the input is changed
// as long as the user hasn't selected an item in the list.
cb.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
final TextField editor = cb.getEditor();
final String selected = cb.getSelectionModel().getSelectedItem();
// This needs run on the GUI thread to avoid the error described
// here: https://bugs.openjdk.java.net/browse/JDK-8081700.
Platform.runLater(() -> {
// If the no item in the list is selected or the selected item
// isn't equal to the current input, we refilter the list.
if (selected == null || !selected.equals(editor.getText())) {
filteredItems.setPredicate(item -> {
// We return true for any items that starts with the
// same letters as the input. We use toUpperCase to
// avoid case sensitivity.
if (item.toUpperCase().startsWith(newValue.toUpperCase())) {
return true;
} else {
return false;
}
});
}
});
});
cb.setItems(filteredItems);
root.getChildren().add(cb);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Take a look:
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
public class FilterComboBox extends ComboBox<String> {
private ObservableList<String> initialList;
private ObservableList<String> bufferList = FXCollections.observableArrayList();
private String previousValue = "";
public FilterComboBox(ObservableList<String> items) {
super(items);
super.setEditable(true);
this.initialList = items;
this.configAutoFilterListener();
}
private void configAutoFilterListener() {
final FilterComboBox currentInstance = this;
this.getEditor().textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
previousValue = oldValue;
final TextField editor = currentInstance.getEditor();
final String selected = currentInstance.getSelectionModel().getSelectedItem();
if (selected == null || !selected.equals(editor.getText())) {
filterItems(newValue, currentInstance);
currentInstance.show();
if (currentInstance.getItems().size() == 1) {
setUserInputToOnlyOption(currentInstance, editor);
}
}
}
});
}
private void filterItems(String filter, ComboBox<String> comboBox) {
if (filter.startsWith(previousValue) && !previousValue.isEmpty()) {
ObservableList<String> filteredList = this.readFromList(filter, bufferList);
bufferList.clear();
bufferList = filteredList;
} else {
bufferList = this.readFromList(filter, initialList);
}
comboBox.setItems(bufferList);
}
private ObservableList<String> readFromList(String filter, ObservableList<String> originalList) {
ObservableList<String> filteredList = FXCollections.observableArrayList();
for (String item : originalList) {
if (item.toLowerCase().startsWith(filter.toLowerCase())) {
filteredList.add(item);
}
}
return filteredList;
}
private void setUserInputToOnlyOption(ComboBox<String> currentInstance, final TextField editor) {
final String onlyOption = currentInstance.getItems().get(0);
final String currentText = editor.getText();
if (onlyOption.length() > currentText.length()) {
editor.setText(onlyOption);
Platform.runLater(new Runnable() {
@Override
public void run() {
editor.selectRange(currentText.length(), onlyOption.length());
}
});
}
}
}
It is based on the answer that is found in this forum. Hope this helps.
I searched similar for a while and found this. Take a look:
public class AutoCompleteComboBoxListener<T> implements EventHandler<KeyEvent> {
private ComboBox comboBox;
private StringBuilder sb;
private ObservableList<T> data;
private boolean moveCaretToPos = false;
private int caretPos;
public AutoCompleteComboBoxListener(final ComboBox comboBox) {
this.comboBox = comboBox;
sb = new StringBuilder();
data = comboBox.getItems();
this.comboBox.setEditable(true);
this.comboBox.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent t) {
comboBox.hide();
}
});
this.comboBox.setOnKeyReleased(AutoCompleteComboBoxListener.this);
}
@Override
public void handle(KeyEvent event) {
ListView lv = ((ComboBoxListViewSkin) comboBox.getSkin()).getListView();
if(event.getCode() == KeyCode.UP) {
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.DOWN) {
if(!comboBox.isShowing()) {
comboBox.show();
}
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.BACK_SPACE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
} else if(event.getCode() == KeyCode.DELETE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
}
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT
|| event.isControlDown() || event.getCode() == KeyCode.HOME
|| event.getCode() == KeyCode.END || event.getCode() == KeyCode.TAB) {
return;
}
ObservableList list = FXCollections.observableArrayList();
for (int i=0; i<data.size(); i++) {
if(data.get(i).toString().toLowerCase().startsWith(
AutoCompleteComboBoxListener.this.comboBox
.getEditor().getText().toLowerCase())) {
list.add(data.get(i));
}
}
String t = comboBox.getEditor().getText();
comboBox.setItems(list);
comboBox.getEditor().setText(t);
if(!moveCaretToPos) {
caretPos = -1;
}
moveCaret(t.length());
if(!list.isEmpty()) {
comboBox.show();
}
}
private void moveCaret(int textLength) {
if(caretPos == -1) {
comboBox.getEditor().positionCaret(textLength);
} else {
comboBox.getEditor().positionCaret(caretPos);
}
moveCaretToPos = false;
}
}
You can call it with
new AutoCompleteComboBoxListener<>(comboBox);
It's based on this and I customized it to fit my needs.
Feel free to use it and if anybody can improve it, tell me.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With