JavaFX - Filtered ComboBox

后端 未结 3 1720
醉酒成梦
醉酒成梦 2020-12-06 05:52

I want a ComboBox, that filteres the list items as the user types. It should work as follow:

  • When typing, the textfield should show one possible selection, b
相关标签:
3条回答
  • 2020-12-06 06:30

    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.

    0 讨论(0)
  • 2020-12-06 06:39

    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();
        }
    }
    
    0 讨论(0)
  • 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.

    0 讨论(0)
提交回复
热议问题