4

I have to create an auto fill ComboBox based on user input.

My code goes like this:

public class JavaFXApplication1 extends Application {

    @Override
    public void start(Stage primaryStage) {
        ComboBox<String> combo = new ComboBox<>();
        ObservableList<String> list = FXCollections.observableArrayList();
        list.add("A");
        list.add("AND");
        list.add("ANDR");
        list.add("ANDRE");
        list.add("B");
        list.add("BP");
        list.add("BPO");
        combo.setItems(list);
        new AutoCompleteComboBoxListener(combo);

        StackPane root = new StackPane();
        root.getChildren().add(combo);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

the AutoCompleteComboBoxListener is taken from this answer.

With this autocomplete works fine. I faced a problem in list size,

Run the application and click on the ComboBox drop down to see popup size.

  • Type ANDRE in the combo box(which will show ANDRE option in popup).Now delete all characters.(by backspace)
  • Now the populated list shrinked to one entry size with scroll bar.
  • Click on the combo box drop down again to get the full size list.

How can I make the list size according to content?

Community
  • 1
  • 1
user3164187
  • 1,382
  • 3
  • 19
  • 50

3 Answers3

4

Not exactly a "list size according to content" (which would be awful if the number of items is too large to fit on the screen, BTW), but you can specify the minimum height of the ListView shown in the popup using CSS.

This ensures the popup size never becomes too small. Even if the number of items is small, this will leave some "empty space" at the bottom of the ListView:

.combo-box .combo-box-popup > .list-view {
    -fx-min-height: 200;
}
Brad Turek
  • 2,472
  • 3
  • 30
  • 56
fabian
  • 80,457
  • 12
  • 86
  • 114
3

This is because the comboBox.hide(); method is not called (the drop-down list is automatically updated on show).

You can improve the listener as:

public class AutoCompleteComboBoxListener<T> implements EventHandler<KeyEvent> {

    private ComboBox<T> comboBox;
    private ObservableList<T> data;
    private boolean moveCaretToPos = false;
    private int caretPos;

    public AutoCompleteComboBoxListener(final ComboBox<T> comboBox) {
        this.comboBox = comboBox;
        data = comboBox.getItems();

        this.comboBox.setEditable(true);
        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;
        } 

        if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT
                || event.isControlDown() || event.getCode() == KeyCode.HOME
                || event.getCode() == KeyCode.END || event.getCode() == KeyCode.TAB) {
            return;
        }

        comboBox.hide();

        if(event.getCode() == KeyCode.BACK_SPACE) {
            moveCaretToPos = true;
            caretPos = comboBox.getEditor().getCaretPosition();
        } else if(event.getCode() == KeyCode.DELETE) {
            moveCaretToPos = true;
            caretPos = comboBox.getEditor().getCaretPosition();
        }



        ObservableList<T> 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;
    }

}

This update will ensure, that the drop-down list will be hidden and re-shown every time the search String has been changed.

DVarga
  • 21,311
  • 6
  • 55
  • 60
  • This can have odd visual effects though, if the popup just disappears for a split second... – fabian Aug 10 '16 at 11:29
  • Yes, I know. The original one was already intended to have this hiding-showing mechanism I guess: `comboBox.hide()` on `keyPress`. I will search how could I have the drop-down list to be resized to the content without this "flickering". – DVarga Aug 10 '16 at 11:41
1

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();
Oleksii Valuiskyi
  • 2,691
  • 1
  • 8
  • 22