I understand this question was answered 9 years ago, however I ran into a similar problem of making a combo box that auto suggests the closest match to what a user enters. Having referenced Mateus Viccari's solution for an auto fill combo box at https://stackoverflow.com/a/27384068/22098160 and Perneel's quesiton at combobox jump to typed char, I was able to form a solution that met my needs quite nicely.
When the user enters any sequence of characters that match with the beginning or all of any of those in the list, only those results that match what the user has entered are displayed in the dropdown with the dropdown menu resized accordingly.
public static void autoFilter(List<String> optionSet) {
methodCalled++;
if (newString.matches("^[A-Za-z0-9_.,'/ ]+$")) {
for (String str : optionSet) {
if (filter != newString.length())
filter = 0;
System.out.println(str);
for (int i = 0; i < newString.length(); i++) {
if (i >= str.length()) {
filter = 0;
break;
}
Character first = (newString.charAt(i));
Character second = (str.charAt(i));
String second1 = second.toString();
System.out.println("[" + second1 + "]");
String first1 = first.toString();
System.out.println("[" + first1 + "]");
if (first1.equalsIgnoreCase(second1)) {
filter++;
System.out.println("Value of count: " + filter);
if (filter == newString.length() && methodCalled == 1) {
cmbBx.hide();
newList.add(str);
cmbBx.setValue(newString);
filter = 0;
}
}
else if (!first1.equalsIgnoreCase(second1)) {
newList.remove(str);
}
}
}
cmbBx.setItems(FXCollections.observableArrayList(newList));
if (caratOffset < 1)
cmbBx.getEditor().positionCaret(newString.length() + 1);
cmbBx.show();
}
}
It should be noted that the matches method's contents contain the space character. This was a problem I ran into as the program did not interpret that the space character was part of the entered string of characters, so this fixes that. Any characters that need to be interpreted can be added inside the square brackets.
A new list is created in the above method which replaces the original observable list passed to the combo box which contains the filtered results.
An issue that seemed to persist was that the carat would move to the incorrect position in the combo box when new text was entered, so this line of code
if (caratOffset < 1)
cmbBx.getEditor().positionCaret(newString.length() + 1);
allowed for correct repositioning. Additionally to allow for proper repositioning, within the key released event handler, the left and right arrow keys now properly position the carat as the user intends.
The method is called by passing it a list, for example
autoFilter(options);
as shown in the start method below:
public void start(Stage primaryStage) throws Exception {
options.add("Hank");
options.add("Hule");
options.add("Holiday");
options.add("Holly");
options.add("Saul");
options.add("Skyler");
options.add("Mike");
options.add("Gus");
options.add("Tuco");
options.add("Nacho");
options.add("Jesse");
options.add("Walter");
options.add("Walter Jr.");
cmbBx.setItems(FXCollections.observableList(options));
cmbBx.setEditable(true);
cmbBx.setOnKeyReleased(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
newString = newString + event.getText();
if (newString.length() > 0) {
cmbBx.addEventFilter(KeyEvent.KEY_RELEASED, event1 -> {
if (event1.getCode() == KeyCode.DOWN) {
cmbBx.show();
event1.consume();
}
else if (event1.getCode() == KeyCode.UP) {
cmbBx.hide();
event1.consume();
}
else if (event1.getCode() == KeyCode.LEFT && caratOffset <= 1) {
cmbBx.getEditor().positionCaret(newString.length() - caratOffset);
caratOffset++;
}
else if (event1.getCode() == KeyCode.BACK_SPACE || event1.getCode() == KeyCode.DELETE) {
// full deletion when text is highlighted
if (cmbBx.getEditor().getCaretPosition() == 0
&& (event1.getCode() == KeyCode.BACK_SPACE || event1.getCode() == KeyCode.DELETE)) {
newString = "";
cmbBx.setValue(newString);
methodCalled = 0;
}
if (methodCalled > 0 && newString.length() != 0) {
newString = newString.substring(0, newString.length() - 1);
}
newList.clear();
filter = 0;
methodCalled = 0;
}
else if (event1.getCode() == KeyCode.ENTER) {
event1.consume();
}
else if (event1.getCode() == KeyCode.SPACE) {
newList.clear();
filter = 0;
methodCalled = 0;
}
});
cmbBx.hide();
autoFilter(options);
}
else if (newString.length() == 0) {
cmbBx.hide();
cmbBx.setItems(FXCollections.observableArrayList(options));
cmbBx.show();
newList.clear();
filter = 0;
methodCalled = 0;
}
cmbBx.setValue(newString);
if (caratOffset < 1)
cmbBx.getEditor().positionCaret(newString.length() + 1);
}
});
}
For anyone wanting to test this, the full code is given below with a sample list:
public class AutoSuggest extends Application {
public static ComboBox cmbBx = new ComboBox();
public static String newString = "";
public static ArrayList<String> options = new ArrayList<>();
public static ArrayList<String> newList = new ArrayList<>();
public static int methodCalled = 0;
public static int filter = 0;
public static int caratOffset = 0;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
options.add("Hank");
options.add("Hule");
options.add("Holiday");
options.add("Holly");
options.add("Saul");
options.add("Skyler");
options.add("Mike");
options.add("Gus");
options.add("Tuco");
options.add("Nacho");
options.add("Jesse");
options.add("Walter");
options.add("Walter Jr.");
cmbBx.setItems(FXCollections.observableList(options));
cmbBx.setEditable(true);
cmbBx.setOnKeyReleased(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
newString = newString + event.getText();
if (newString.length() > 0) {
cmbBx.addEventFilter(KeyEvent.KEY_RELEASED, event1 -> {
if (event1.getCode() == KeyCode.DOWN) {
cmbBx.show();
event1.consume();
}
else if (event1.getCode() == KeyCode.UP) {
cmbBx.hide();
event1.consume();
}
else if (event1.getCode() == KeyCode.LEFT && caratOffset <= 1) {
cmbBx.getEditor().positionCaret(newString.length() - caratOffset);
caratOffset++;
}
else if (event1.getCode() == KeyCode.BACK_SPACE || event1.getCode() == KeyCode.DELETE) {
// full deletion when text is highlighted
if (cmbBx.getEditor().getCaretPosition() == 0
&& (event1.getCode() == KeyCode.BACK_SPACE || event1.getCode() == KeyCode.DELETE)) {
newString = "";
cmbBx.setValue(newString);
methodCalled = 0;
}
if (methodCalled > 0 && newString.length() != 0) {
newString = newString.substring(0, newString.length() - 1);
}
newList.clear();
filter = 0;
methodCalled = 0;
}
else if (event1.getCode() == KeyCode.ENTER) {
event1.consume();
}
else if (event1.getCode() == KeyCode.SPACE) {
newList.clear();
filter = 0;
methodCalled = 0;
}
});
cmbBx.hide();
autoFilter(options);
}
else if (newString.length() == 0) {
cmbBx.hide();
cmbBx.setItems(FXCollections.observableArrayList(options));
cmbBx.show();
newList.clear();
filter = 0;
methodCalled = 0;
}
cmbBx.setValue(newString);
if (caratOffset < 1)
cmbBx.getEditor().positionCaret(newString.length() + 1);
}
});
BorderPane root = new BorderPane();
root.setCenter(cmbBx);
Scene scene = new Scene(root, 250, 250);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setTitle("Auto Suggest ComboBox");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
public static void autoFilter(List<String> optionSet) {
methodCalled++;
if (newString.matches("^[A-Za-z0-9_.,'/ ]+$")) {
for (String str : optionSet) {
if (filter != newString.length())
filter = 0;
System.out.println(str);
for (int i = 0; i < newString.length(); i++) {
if (i >= str.length()) {
filter = 0;
break;
}
Character first = (newString.charAt(i));
Character second = (str.charAt(i));
String second1 = second.toString();
System.out.println("[" + second1 + "]");
String first1 = first.toString();
System.out.println("[" + first1 + "]");
if (first1.equalsIgnoreCase(second1)) {
filter++;
System.out.println("Value of count: " + filter);
if (filter == newString.length() && methodCalled == 1) {
cmbBx.hide();
newList.add(str);
cmbBx.setValue(newString);
filter = 0;
}
}
else if (!first1.equalsIgnoreCase(second1)) {
newList.remove(str);
}
}
}
cmbBx.setItems(FXCollections.observableArrayList(newList));
if (caratOffset < 1)
cmbBx.getEditor().positionCaret(newString.length() + 1);
cmbBx.show();
}
}
}
Please feel free to use this solution or any portion of this code and do not hesitate to contact me if there are any questions or concerns.