AutoComplete ComboBox in JavaFX

JavaComboboxAutocompleteJavafx 2Javafx 8

Java Problem Overview


I'm looking for a way to add autocomplete to a JavaFX ComboBox.

This AutoFillBox is known but not what I'm searching. What I want is a editable ComboBox, and while typing the list should filtered. But I want also to open the list without typing and seeing the whole items.

Any idea?

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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.

Solution 3 - Java

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

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

Solution 4 - Java

Based on Jonatan's answer, I was able to build the following solution:

enter image description here

enter image description here

enter image description here

enter image description here

import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.*;
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.Control;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.util.ArrayList;
import java.util.List;

public class Main extends Application
{
    public static class HideableItem<T>
    {
        private final ObjectProperty<T> object = new SimpleObjectProperty<>();
        private final BooleanProperty hidden = new SimpleBooleanProperty();
        
        private HideableItem(T object)
        {
            setObject(object);
        }
        
        private ObjectProperty<T> objectProperty(){return this.object;}
        private T getObject(){return this.objectProperty().get();}
        private void setObject(T object){this.objectProperty().set(object);}
        
        private BooleanProperty hiddenProperty(){return this.hidden;}
        private boolean isHidden(){return this.hiddenProperty().get();}
        private void setHidden(boolean hidden){this.hiddenProperty().set(hidden);}
        
        @Override
        public String toString()
        {
            return getObject() == null ? null : getObject().toString();
        }
    }
    
    public void start(Stage stage)
    {
        List<String> countries = new ArrayList<>();
        
        countries.add("Afghanistan");
        countries.add("Albania");
        countries.add("Algeria");
        countries.add("Andorra");
        countries.add("Angola");
        countries.add("Antigua and Barbuda");
        countries.add("Argentina");
        countries.add("Armenia");
        countries.add("Australia");
        countries.add("Austria");
        countries.add("Azerbaijan");
        countries.add("Bahamas");
        countries.add("Bahrain");
        countries.add("Bangladesh");
        countries.add("Barbados");
        countries.add("Belarus");
        countries.add("Belgium");
        countries.add("Belize");
        countries.add("Benin");
        countries.add("Bhutan");
        countries.add("Bolivia");
        countries.add("Bosnia and Herzegovina");
        countries.add("Botswana");
        countries.add("Brazil");
        countries.add("Brunei");
        countries.add("Bulgaria");
        countries.add("Burkina Faso");
        countries.add("Burundi");
        countries.add("Cabo Verde");
        countries.add("Cambodia");
        countries.add("Cameroon");
        countries.add("Canada");
        countries.add("Central African Republic (CAR)");
        countries.add("Chad");
        countries.add("Chile");
        countries.add("China");
        countries.add("Colombia");
        countries.add("Comoros");
        countries.add("Democratic Republic of the Congo");
        countries.add("Republic of the Congo");
        countries.add("Costa Rica");
        countries.add("Cote d'Ivoire");
        countries.add("Croatia");
        countries.add("Cuba");
        countries.add("Cyprus");
        countries.add("Czech Republic");
        countries.add("Denmark");
        countries.add("Djibouti");
        countries.add("Dominica");
        countries.add("Dominican Republic");
        countries.add("Ecuador");
        countries.add("Egypt");
        countries.add("El Salvador");
        countries.add("Equatorial Guinea");
        countries.add("Eritrea");
        countries.add("Estonia");
        countries.add("Ethiopia");
        countries.add("Fiji");
        countries.add("Finland");
        countries.add("France");
        countries.add("Gabon");
        countries.add("Gambia");
        countries.add("Georgia");
        countries.add("Germany");
        countries.add("Ghana");
        countries.add("Greece");
        countries.add("Grenada");
        countries.add("Guatemala");
        countries.add("Guinea");
        countries.add("Guinea-Bissau");
        countries.add("Guyana");
        countries.add("Haiti");
        countries.add("Honduras");
        countries.add("Hungary");
        countries.add("Iceland");
        countries.add("India");
        countries.add("Indonesia");
        countries.add("Iran");
        countries.add("Iraq");
        countries.add("Ireland");
        countries.add("Israel");
        countries.add("Italy");
        countries.add("Jamaica");
        countries.add("Japan");
        countries.add("Jordan");
        countries.add("Kazakhstan");
        countries.add("Kenya");
        countries.add("Kiribati");
        countries.add("Kosovo");
        countries.add("Kuwait");
        countries.add("Kyrgyzstan");
        countries.add("Laos");
        countries.add("Latvia");
        countries.add("Lebanon");
        countries.add("Lesotho");
        countries.add("Liberia");
        countries.add("Libya");
        countries.add("Liechtenstein");
        countries.add("Lithuania");
        countries.add("Luxembourg");
        countries.add("Macedonia (FYROM)");
        countries.add("Madagascar");
        countries.add("Malawi");
        countries.add("Malaysia");
        countries.add("Maldives");
        countries.add("Mali");
        countries.add("Malta");
        countries.add("Marshall Islands");
        countries.add("Mauritania");
        countries.add("Mauritius");
        countries.add("Mexico");
        countries.add("Micronesia");
        countries.add("Moldova");
        countries.add("Monaco");
        countries.add("Mongolia");
        countries.add("Montenegro");
        countries.add("Morocco");
        countries.add("Mozambique");
        countries.add("Myanmar (Burma)");
        countries.add("Namibia");
        countries.add("Nauru");
        countries.add("Nepal");
        countries.add("Netherlands");
        countries.add("New Zealand");
        countries.add("Nicaragua");
        countries.add("Niger");
        countries.add("Nigeria");
        countries.add("North Korea");
        countries.add("Norway");
        countries.add("Oman");
        countries.add("Pakistan");
        countries.add("Palau");
        countries.add("Palestine");
        countries.add("Panama");
        countries.add("Papua New Guinea");
        countries.add("Paraguay");
        countries.add("Peru");
        countries.add("Philippines");
        countries.add("Poland");
        countries.add("Portugal");
        countries.add("Qatar");
        countries.add("Romania");
        countries.add("Russia");
        countries.add("Rwanda");
        countries.add("Saint Kitts and Nevis");
        countries.add("Saint Lucia");
        countries.add("Saint Vincent and the Grenadines");
        countries.add("Samoa");
        countries.add("San Marino");
        countries.add("Sao Tome and Principe");
        countries.add("Saudi Arabia");
        countries.add("Senegal");
        countries.add("Serbia");
        countries.add("Seychelles");
        countries.add("Sierra Leone");
        countries.add("Singapore");
        countries.add("Slovakia");
        countries.add("Slovenia");
        countries.add("Solomon Islands");
        countries.add("Somalia");
        countries.add("South Africa");
        countries.add("South Korea");
        countries.add("South Sudan");
        countries.add("Spain");
        countries.add("Sri Lanka");
        countries.add("Sudan");
        countries.add("Suriname");
        countries.add("Swaziland");
        countries.add("Sweden");
        countries.add("Switzerland");
        countries.add("Syria");
        countries.add("Taiwan");
        countries.add("Tajikistan");
        countries.add("Tanzania");
        countries.add("Thailand");
        countries.add("Timor-Leste");
        countries.add("Togo");
        countries.add("Tonga");
        countries.add("Trinidad and Tobago");
        countries.add("Tunisia");
        countries.add("Turkey");
        countries.add("Turkmenistan");
        countries.add("Tuvalu");
        countries.add("Uganda");
        countries.add("Ukraine");
        countries.add("United Arab Emirates (UAE)");
        countries.add("United Kingdom (UK)");
        countries.add("United States of America (USA)");
        countries.add("Uruguay");
        countries.add("Uzbekistan");
        countries.add("Vanuatu");
        countries.add("Vatican City (Holy See)");
        countries.add("Venezuela");
        countries.add("Vietnam");
        countries.add("Yemen");
        countries.add("Zambia");
        countries.add("Zimbabwe");
        
        ComboBox<HideableItem<String>> comboBox = createComboBoxWithAutoCompletionSupport(countries);
        comboBox.setMaxWidth(Double.MAX_VALUE);
        
        HBox root = new HBox();
        root.getChildren().add(comboBox);
        
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
        
        comboBox.setMinWidth(comboBox.getWidth());
        comboBox.setPrefWidth(comboBox.getWidth());
    }
    
    public static void main(String[] args)
    {
        launch();
    }
    
    private static <T> ComboBox<HideableItem<T>> createComboBoxWithAutoCompletionSupport(List<T> items)
    {
        ObservableList<HideableItem<T>> hideableHideableItems = FXCollections.observableArrayList(hideableItem -> new Observable[]{hideableItem.hiddenProperty()});
        
        items.forEach(item ->
        {
            HideableItem<T> hideableItem = new HideableItem<>(item);
            hideableHideableItems.add(hideableItem);
        });
        
        FilteredList<HideableItem<T>> filteredHideableItems = new FilteredList<>(hideableHideableItems, t -> !t.isHidden());
        
        ComboBox<HideableItem<T>> comboBox = new ComboBox<>();
        comboBox.setItems(filteredHideableItems);
        
        @SuppressWarnings("unchecked")
        HideableItem<T>[] selectedItem = (HideableItem<T>[]) new HideableItem[1];
        
        comboBox.addEventHandler(KeyEvent.KEY_PRESSED, event ->
        {
            if(!comboBox.isShowing()) return;
            
            comboBox.setEditable(true);
            comboBox.getEditor().clear();
        });
        
        comboBox.showingProperty().addListener((observable, oldValue, newValue) ->
        {
            if(newValue)
            {
                @SuppressWarnings("unchecked")
                ListView<HideableItem> lv = ((ComboBoxListViewSkin<HideableItem>) comboBox.getSkin()).getListView();
                
                Platform.runLater(() ->
                {
                    if(selectedItem[0] == null) // first use
                    {
                        double cellHeight = ((Control) lv.lookup(".list-cell")).getHeight();
                        lv.setFixedCellSize(cellHeight);
                    }
                });
                
                lv.scrollTo(comboBox.getValue());
            }
            else
            {
                HideableItem<T> value = comboBox.getValue();
                if(value != null) selectedItem[0] = value;
                
                comboBox.setEditable(false);
                
                Platform.runLater(() ->
                {
                    comboBox.getSelectionModel().select(selectedItem[0]);
                    comboBox.setValue(selectedItem[0]);
                });
            }
        });
        
        comboBox.setOnHidden(event -> hideableHideableItems.forEach(item -> item.setHidden(false)));
        
        comboBox.getEditor().textProperty().addListener((obs, oldValue, newValue) ->
        {
            if(!comboBox.isShowing()) return;
            
            Platform.runLater(() ->
            {
                if(comboBox.getSelectionModel().getSelectedItem() == null)
                {
                    hideableHideableItems.forEach(item -> item.setHidden(!item.getObject().toString().toLowerCase().contains(newValue.toLowerCase())));
                }
                else
                {
                    boolean validText = false;
                    
                    for(HideableItem hideableItem : hideableHideableItems)
                    {
                        if(hideableItem.getObject().toString().equals(newValue))
                        {
                            validText = true;
                            break;
                        }
                    }
                    
                    if(!validText) comboBox.getSelectionModel().select(null);
                }
            });
        });
        
        return comboBox;
    }
}

UPDATE:

In Java 9+, you can access the ListView like this:

ListView<ComboBoxItem> lv = (ListView<ComboBoxItem>) ((ComboBoxListViewSkin<?>) comboBox.getSkin()).getPopupContent();

Solution 5 - Java

I found Eng Fouad's answer to be the BEST overall (even for 10,000+ items) however, I had to fix 3 bugs:

  1. When you click SHIFT, the editor disappeared

  2. When you typed SPACE, the ComboBox would close

  3. When you "cleared the selection" and then opened the combobox and closed it without selecting anything, it would re-select the last item.

I also added a passing of a StringConverter, used Apache StringUtils for comparing as well as moved Streams to regular for-loops for performance purposes as per: https://blog.jooq.org/2015/12/08/3-reasons-why-you-shouldnt-replace-your-for-loops-by-stream-foreach/

Also as per Eng Fouad's answer,

In Java 9+, you can access the ListView like this:

ListView<ComboBoxItem> lv = (ListView<ComboBoxItem>) ((ComboBoxListViewSkin<?>) comboBox.getSkin()).getPopupContent();

Example Usage:

List<Locale> locales = Arrays.asList(Locale.getAvailableLocales());
ComboBox<HideableItem<Locale>> dropDown = AutoCompleteComboBox.createComboBoxWithAutoCompletionSupport(locales, new StringConverter<HideableItem<Locale>>()
{
                        
    @Override
    public String toString(HideableItem<Locale> object)
    {
        if(object!=null)
        {                    
            return object.getObject().getDisplayName();
        }else
        {
           return null;
        }
    }

    @Override
    public HideableItem<Locale> fromString(String string)
    {
        Locale foundLocale = locales.stream().filter((Locale i)
                        -> (i.getDisplayName()).equals(string)).findFirst().orElse(null);
        return new HideableItem(foundLocale, this);
    }
    
});

AutoCompleteComboBox.java

import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.Event;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
import org.apache.commons.lang3.StringUtils;

public class AutoCompleteComboBox
{

    public static class HideableItem<T>
    {

        private final ObjectProperty<T> object = new SimpleObjectProperty<>();
        private final BooleanProperty hidden = new SimpleBooleanProperty();
        private StringConverter converter;

        public HideableItem(T object, StringConverter converter)
        {
            setConverter(converter);
            setObject(object);

        }

        private ObjectProperty<T> objectProperty()
        {
            return this.object;
        }

        public T getObject()
        {
            return this.objectProperty().get();
        }

        private void setObject(T object)
        {
            this.objectProperty().set(object);
        }

        private BooleanProperty hiddenProperty()
        {
            return this.hidden;
        }

        private boolean isHidden()
        {
            return this.hiddenProperty().get();
        }

        private void setHidden(boolean hidden)
        {
            this.hiddenProperty().set(hidden);
        }

        private void setConverter(StringConverter converter)
        {
            this.converter = converter;
        }

        @Override
        public String toString()
        {
            return getObject() == null ? null : converter.toString(this);
        }
    }

    public static <T> ComboBox<HideableItem<T>> createComboBoxWithAutoCompletionSupport(List<T> items, StringConverter converter)
    {
        ObservableList<HideableItem<T>> hideableHideableItems = FXCollections.observableArrayList(hideableItem -> new Observable[]
        {
            hideableItem.hiddenProperty()
        });

        for (T item : items)
        {
            HideableItem<T> hideableItem = new HideableItem<>(item, converter);
            hideableHideableItems.add(hideableItem);
        }

        FilteredList<HideableItem<T>> filteredHideableItems = new FilteredList<>(hideableHideableItems, t -> !t.isHidden());

        ComboBox<HideableItem<T>> comboBox = new ComboBox<>();
        comboBox.setItems(filteredHideableItems);
        comboBox.setConverter(converter);

        ComboBoxListViewSkin<HideableItem<T>> comboBoxListViewSkin = new ComboBoxListViewSkin<HideableItem<T>>(comboBox);
        comboBoxListViewSkin.getPopupContent().addEventFilter(KeyEvent.ANY, (KeyEvent event) ->
        {
            if (event.getCode() == KeyCode.SPACE)
            {
                event.consume();
            }
        });
        comboBox.setSkin(comboBoxListViewSkin);

        @SuppressWarnings("unchecked")
        HideableItem<T>[] selectedItem = (HideableItem<T>[]) new HideableItem[1];

        comboBox.addEventHandler(KeyEvent.KEY_PRESSED, event ->
        {
            if (!comboBox.isShowing() || event.isShiftDown() || event.isControlDown())
            {
                return;
            }
            comboBox.setEditable(true);
            comboBox.getEditor().clear();
        });

        comboBox.showingProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) ->
        {
            if (newValue)
            {
                @SuppressWarnings("unchecked")
                ListView<HideableItem> lv = ((ComboBoxListViewSkin<HideableItem>) comboBox.getSkin()).getListView();

                Platform.runLater(() ->
                {
                    if (selectedItem[0] == null) // first use
                    {
                        double cellHeight = ((Control) lv.lookup(".list-cell")).getHeight();
                        lv.setFixedCellSize(cellHeight);
                    }
                });

                lv.scrollTo(comboBox.getValue());
            } else
            {

                HideableItem<T> value = comboBox.getValue();
                if (value != null)
                {
                    selectedItem[0] = value;
                }

                comboBox.setEditable(false);
                if (value != null)
                {
                    Platform.runLater(() ->
                    {
                        comboBox.getSelectionModel().select(selectedItem[0]);
                        comboBox.setValue(selectedItem[0]);
                    });
                }

            }
        });

        comboBox.setOnHidden((Event event) ->
        {
            for (HideableItem item : hideableHideableItems)
            {
                item.setHidden(false);
            }
        });
        comboBox.valueProperty().addListener((ObservableValue<? extends HideableItem<T>> obs, HideableItem<T> oldValue, HideableItem<T> newValue) ->
        {
            if (newValue == null)
            {
                for (HideableItem item : hideableHideableItems)
                {
                    item.setHidden(false);
                }
            }
        });

        comboBox.getEditor().textProperty().addListener((ObservableValue<? extends String> obs, String oldValue, String newValue) ->
        {
            if (!comboBox.isShowing())
            {
                return;
            }

            Platform.runLater(() ->
            {
                if (comboBox.getSelectionModel().getSelectedItem() == null)
                {
                    for (HideableItem item : hideableHideableItems)
                    {
                        item.setHidden(!StringUtils.containsIgnoreCase(item.toString(), newValue));
                    }
                } else
                {

                    boolean validText = false;

                    for (HideableItem hideableItem : hideableHideableItems)
                    {
                        if (hideableItem.toString().equals(newValue))
                        {
                            validText = true;
                            break;
                        }
                    }

                    if (!validText)
                    {
                        comboBox.getSelectionModel().select(null);
                    }
                }
            });
        });

        return comboBox;
    }
}

Solution 6 - Java

I look around and try something. This look good:

public void handle( KeyEvent event ) {
        if( event.getCode() == KeyCode.BACK_SPACE)
            s = s.substring( 0, s.length() - 1 );
        else s += event.getText();
        for( String item: items ) {
            if( item.startsWith( s ) ) sm.select( item );
        }
    }

A Keyhandle for select the item with matching start charakter.

I hope this help you

Solution 7 - Java

A bit late for the party, but I came across this thread when I had exactly this problem and really liked the answers and the approach, so thanks to everyone who contributed.

But, I don't know what you guys are putting in your ComboBox, whenever I left the box without making a specific selection a ClassCastException was thrown. So I am guessing you all mainly use the ComboBox for picking Strings, so I had to come up with a StringConverter since I am using the ComboBox for Objects (FilterCriteria).

So here is the converter, hopefully helpful to someone.

private final StringConverter<FilterCriteria> filterCriteriaStringConverter = new StringConverter<FilterCriteria>()
{
    @Override public String toString(FilterCriteria filterCriteria)
    {
        if (filterCriteria == null)
        {
            return "";
        } else
        {
            return filterCriteria.getName();
        }
    }

    @Override public FilterCriteria fromString(String string)
    {
        Optional<FilterCriteria> optionalFilterCriteria = availableTypesComboBox.getItems().stream()
          .filter(filterCriteria -> filterCriteria.getName().contains(string))
          .findFirst();
        return optionalFilterCriteria.orElseGet(() -> availableTypesComboBox.getItems().get(0));
    }
};

Solution 8 - Java

If you use ControlsFx then SearchableComboBox is a simple extension of a ComboBox which shows a search field while the popup is showing. The user can type any text into this search field to filter the popup list.

enter image description here

Solution 9 - Java

To add to Mateus' code, the following will create prompt text for auto-completion. For example, if you typed "s", an item beginning with "s" from the ObservableArray (which populated the ComboBox) will serve as the prompt text. Obviously, it would not make much sense to use it with "CONTAINING" parameter.

public class ACComboBox1 {
static String some;
static String typedText;
static StringBuilder sb = new StringBuilder();
public enum AutoCompleteMode {
    STARTS_WITH,CONTAINING,;
}


public static<T> void autoCompleteComboBox(ComboBox<T> comboBox, AutoCompleteMode mode) {

    ObservableList<T> data = comboBox.getItems();
    
    comboBox.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
        public void handle(KeyEvent event){
       
        comboBox.hide();
        }
            });
    comboBox.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
        private boolean moveCaretToPos = false;
        private int caretPos;
        public void handle(KeyEvent event) {
       
            String keyPressed = event.getCode().toString().toLowerCase();
         
            
         
            if ("space".equals(keyPressed) ){
                typedText= " ";
            } else if ("shift".equals(keyPressed ) || "command".equals(keyPressed)
                   || "alt".equals(keyPressed) ) {
              
                return;
            } else  {
                typedText = event.getCode().toString().toLowerCase();
            }
            
           
            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();
                typedText=null;
                sb.delete(0, sb.length());
                comboBox.getEditor().setText(null);
                return;
                
            } else if (event.getCode() == KeyCode.DELETE) {
                moveCaretToPos = true;
                caretPos = comboBox.getEditor().getCaretPosition();
                return;
            } else if (event.getCode().equals(KeyCode.TAB)) {
           
                some = null;
                typedText = null;
                sb.delete(0, sb.length());
                return;
            } else if (event.getCode() == KeyCode.LEFT
                    || event.isControlDown() || event.getCode() == KeyCode.HOME
                    || event.getCode() == KeyCode.END || event.getCode() == KeyCode.RIGHT) {
           
                return;
            }
           
           
            if (typedText==null){
            typedText = comboBox.getEditor().getText().toLowerCase();
            sb.append(typedText);
          
            }else{
                System.out.println("sb:"+sb);
                System.out.println("tt:"+typedText);
                
                sb.append(typedText);
                
              
            } 
           
            ObservableList<T> list = FXCollections.observableArrayList();
            for (T aData : data) {
            
                  if (mode.equals(AutoCompleteMode.STARTS_WITH) && aData.toString().toLowerCase().startsWith(sb.toString())) {
                    list.add(aData);
                    some = aData.toString();
                } else if (mode.equals(AutoCompleteMode.CONTAINING) && aData.toString().toLowerCase().contains(comboBox.getEditor().getText().toLowerCase())) {
                    list.add(aData);
                }
            }
            
             
            comboBox.setItems(list);
          
            comboBox.getEditor().setText(some);
           
            
            
            comboBox.getEditor().positionCaret(sb.length());
            comboBox.getEditor().selectEnd();
            if (!moveCaretToPos) {
                caretPos = -1;
            }
            
            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());
    }
}

}

Solution 10 - Java

here is a simple one

public class AutoShowComboBoxHelper {
	public AutoShowComboBoxHelper(final ComboBox<String> comboBox, final Callback<String, String> textBuilder) {
		final ObservableList<String> items = FXCollections.observableArrayList(comboBox.getItems());

		comboBox.getEditor().textProperty().addListener((ov, o, n) -> {
			if (n.equals(comboBox.getSelectionModel().getSelectedItem())) {
				return;
			}

			comboBox.hide();
			final FilteredList<String> filtered = items.filtered(s -> textBuilder.call(s).toLowerCase().contains(n.toLowerCase()));
			if (filtered.isEmpty()) {
				comboBox.getItems().setAll(items);
			} else {
				comboBox.getItems().setAll(filtered);
				comboBox.show();
			}
		});
	}
}

and a way to use it:

new AutoShowComboBoxHelper(combo, item -> buildTextToCompare(item));

For the sake of simplicity I used String::contains in this code, for better performance use org.apache.commons.lang3.StringUtils::containsIgnoreCase

Solution 11 - Java

I suggest to try solution from small utility library jalvafx

List<String> items = Arrays.asList("Mercury", 
                                   "Venus", 
                                   "Earth", 
                                   "Mars", 
                                   "Jupiter", 
                                   "Saturn", 
                                   "Neptune");

ComboBoxCustomizer.create(comboBox)
                  .autocompleted(items)
                  .customize();

By default, double click to clear value. There are some other usefull features. You can add extra columns or glyphs, single out specific items, change items default toString representation ...

ComboBoxCustomizer.create(comboBox)
                  .autocompleted(items)
                  .overrideToString(o -> "planet: " + o)
                  .multyColumn(o -> Arrays.asList("column 2", "column 3"))
                  .emphasized(o -> o.endsWith("s"))
                  .customize();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJulianGView Question on Stackoverflow
Solution 1 - JavaMateus ViccariView Answer on Stackoverflow
Solution 2 - JavaJulianGView Answer on Stackoverflow
Solution 3 - JavaKorvin GumpView Answer on Stackoverflow
Solution 4 - JavaEng.FouadView Answer on Stackoverflow
Solution 5 - JavatrilogyView Answer on Stackoverflow
Solution 6 - JavaKarlOiView Answer on Stackoverflow
Solution 7 - JavaSamarekView Answer on Stackoverflow
Solution 8 - JavaManusha KarunathilakaView Answer on Stackoverflow
Solution 9 - JavaRounakView Answer on Stackoverflow
Solution 10 - JavaFredView Answer on Stackoverflow
Solution 11 - Javaalex valuiskyiView Answer on Stackoverflow