AutoComplete ComboBox in JavaFX AutoComplete ComboBox in JavaFX java java

AutoComplete ComboBox in JavaFX


First, you'll have to create this class in your project:

import javafx.collections.FXCollections;import javafx.collections.ObservableList;import javafx.event.EventHandler;import javafx.scene.control.ComboBox;import javafx.scene.input.KeyCode;import javafx.scene.input.KeyEvent;public class FxUtilTest {    public interface AutoCompleteComparator<T> {        boolean matches(String typedText, T objectToCompare);    }    public static<T> void autoCompleteComboBoxPlus(ComboBox<T> comboBox, AutoCompleteComparator<T> comparatorMethod) {        ObservableList<T> data = comboBox.getItems();        comboBox.setEditable(true);        comboBox.getEditor().focusedProperty().addListener(observable -> {            if (comboBox.getSelectionModel().getSelectedIndex() < 0) {                comboBox.getEditor().setText(null);            }        });        comboBox.addEventHandler(KeyEvent.KEY_PRESSED, t -> comboBox.hide());        comboBox.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {            private boolean moveCaretToPos = false;            private int caretPos;            @Override            public void handle(KeyEvent event) {                if (event.getCode() == KeyCode.UP) {                    caretPos = -1;                    if (comboBox.getEditor().getText() != null) {                        moveCaret(comboBox.getEditor().getText().length());                    }                    return;                } else if (event.getCode() == KeyCode.DOWN) {                    if (!comboBox.isShowing()) {                        comboBox.show();                    }                    caretPos = -1;                    if (comboBox.getEditor().getText() != null) {                        moveCaret(comboBox.getEditor().getText().length());                    }                    return;                } else if (event.getCode() == KeyCode.BACK_SPACE) {                    if (comboBox.getEditor().getText() != null) {                        moveCaretToPos = true;                        caretPos = comboBox.getEditor().getCaretPosition();                    }                } else if (event.getCode() == KeyCode.DELETE) {                    if (comboBox.getEditor().getText() != null) {                        moveCaretToPos = true;                        caretPos = comboBox.getEditor().getCaretPosition();                    }                } else if (event.getCode() == KeyCode.ENTER) {                    return;                }                if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT || event.getCode().equals(KeyCode.SHIFT) || event.getCode().equals(KeyCode.CONTROL)                        || event.isControlDown() || event.getCode() == KeyCode.HOME                        || event.getCode() == KeyCode.END || event.getCode() == KeyCode.TAB) {                    return;                }                ObservableList<T> list = FXCollections.observableArrayList();                for (T aData : data) {                    if (aData != null && comboBox.getEditor().getText() != null && comparatorMethod.matches(comboBox.getEditor().getText(), aData)) {                        list.add(aData);                    }                }                String t = "";                if (comboBox.getEditor().getText() != null) {                    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;            }        });    }    public static<T> T getComboBoxValue(ComboBox<T> comboBox){        if (comboBox.getSelectionModel().getSelectedIndex() < 0) {            return null;        } else {            return comboBox.getItems().get(comboBox.getSelectionModel().getSelectedIndex());        }    }}

To make your ComboBox autocomplete, use it like this:

FxUtilTest.autoCompleteComboBoxPlus(myComboBox, (typedText, itemToCompare) -> itemToCompare.getName().toLowerCase().contains(typedText.toLowerCase()) || itemToCompare.getAge().toString().equals(typedText));

Then, add a StringConverter like the following example (because the ComboBox value will return a String and it has to be converted into your object):

myComboBox.setConverter(new StringConverter<>() {    @Override    public String toString(YourObject object) {        return object != null ? object.getName() : "";    }    @Override    public YourObject fromString(String string) {        return myComboBox.getItems().stream().filter(object ->                object.getName().equals(string)).findFirst().orElse(null);    }});

Also be sure to use this method when you need to get the selected value from the combobox, otherwise you may face some exceptions like "class cast exception":

FxUtilTest.getComboBoxValue(myComboBox);

P.S.: There was some problems with this method in versions between JRE 8.51 and 8.65 which caused some weird behaviors, now the problems seem not to happen anymore. If you face some issue, you can see the edits made on this answer and get the older version which fixed the problem at the time.This method must work fine, if you face any problem, please, let me know.


I found a solution that's working for me:

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) {        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.


With ControlsFX library you can do it with two lines of code:

comboBox.setEditable(true);TextFields.bindAutoCompletion(comboBox.getEditor(), comboBox.getItems());