JavaFX TextField Auto-suggestions

后端 未结 5 733
我寻月下人不归
我寻月下人不归 2020-11-29 07:12

I want to make this TextField have suggestions feature just like in Lucene. I\'ve searched all the web and I just find it for ComboBox.

TextField instNameTxt         


        
5条回答
  •  渐次进展
    2020-11-29 07:47

    Here is my solution based on This.

    public class AutocompletionlTextField extends TextFieldWithLengthLimit {
        //Local variables
        //entries to autocomplete
        private final SortedSet entries;      
        //popup GUI
        private ContextMenu entriesPopup;
    
    
        public AutocompletionlTextField() {
            super();
            this.entries = new TreeSet<>();
            this.entriesPopup = new ContextMenu();
    
            setListner();
        }  
    
    
        /**
         * wrapper for default constructor with setting of "TextFieldWithLengthLimit" LengthLimit
         * 
         * @param lengthLimit 
         */
        public AutocompletionlTextField(int lengthLimit) {        
            this();
            super.setLengthLimit(lengthLimit);                
        }
    
    
        /**
         * "Suggestion" specific listners
         */
        private void setListner() {     
            //Add "suggestions" by changing text
            textProperty().addListener((observable, oldValue, newValue) -> {
                String enteredText = getText();
                //always hide suggestion if nothing has been entered (only "spacebars" are dissalowed in TextFieldWithLengthLimit)
                if (enteredText == null || enteredText.isEmpty()) {
                    entriesPopup.hide();
                } else {
                    //filter all possible suggestions depends on "Text", case insensitive
                    List filteredEntries = entries.stream()
                            .filter(e -> e.toLowerCase().contains(enteredText.toLowerCase()))
                            .collect(Collectors.toList());
                    //some suggestions are found
                    if (!filteredEntries.isEmpty()) {
                        //build popup - list of "CustomMenuItem"
                        populatePopup(filteredEntries, enteredText);
                        if (!entriesPopup.isShowing()) { //optional
                            entriesPopup.show(AutocompletionlTextField.this, Side.BOTTOM, 0, 0); //position of popup
                        }
                    //no suggestions -> hide
                    } else {
                        entriesPopup.hide();
                    }
                }
            });
    
            //Hide always by focus-in (optional) and out
            focusedProperty().addListener((observableValue, oldValue, newValue) -> {
                entriesPopup.hide();
            });
        }             
    
    
        /**
        * Populate the entry set with the given search results. Display is limited to 10 entries, for performance.
        * 
        * @param searchResult The set of matching strings.
        */
        private void populatePopup(List searchResult, String searchReauest) {
            //List of "suggestions"
            List menuItems = new LinkedList<>();
            //List size - 10 or founded suggestions count
            int maxEntries = 10;
            int count = Math.min(searchResult.size(), maxEntries);
            //Build list as set of labels
            for (int i = 0; i < count; i++) {
              final String result = searchResult.get(i);
              //label with graphic (text flow) to highlight founded subtext in suggestions
              Label entryLabel = new Label();
              entryLabel.setGraphic(Styles.buildTextFlow(result, searchReauest));  
              entryLabel.setPrefHeight(10);  //don't sure why it's changed with "graphic"
              CustomMenuItem item = new CustomMenuItem(entryLabel, true);
              menuItems.add(item);
    
              //if any suggestion is select set it into text and close popup
              item.setOnAction(actionEvent -> {
                  setText(result);
                  positionCaret(result.length());
                  entriesPopup.hide();
              });
            }
    
            //"Refresh" context menu
            entriesPopup.getItems().clear();
            entriesPopup.getItems().addAll(menuItems);
        }
    
    
        /**
        * Get the existing set of autocomplete entries.
        * 
        * @return The existing autocomplete entries.
        */
        public SortedSet getEntries() { return entries; }
    }
    

    You must extends from "TextField" instead of "TextFieldWithLengthLimit" and delete constructor with "Length limit".

    I use static methods to work with Styles. It's used here to "highlight" entered text inside suggestion results. Here is the code of methos from this class:

    /**
     * Build TextFlow with selected text. Return "case" dependent.
     * 
     * @param text - string with text
     * @param filter - string to select in text
     * @return - TextFlow
     */
    public static TextFlow buildTextFlow(String text, String filter) {        
        int filterIndex = text.toLowerCase().indexOf(filter.toLowerCase());
        Text textBefore = new Text(text.substring(0, filterIndex));
        Text textAfter = new Text(text.substring(filterIndex + filter.length()));
        Text textFilter = new Text(text.substring(filterIndex,  filterIndex + filter.length())); //instead of "filter" to keep all "case sensitive"
        textFilter.setFill(Color.ORANGE);
        textFilter.setFont(Font.font("Helvetica", FontWeight.BOLD, 12));  
        return new TextFlow(textBefore, textFilter, textAfter);
    }    
    

    You may add this "AutocompletionlTextField" in FXML (dont forget about "imports") or inside constructor. To set "suggestions" list on use "entries" getter:

    AutocompletionlTextField field = new AutocompletionlTextField();
    field.getEntries().addAll(YOUR_ARRAY_OF_STRINGS);
    

    It seems like that in my application:

    Hope it helps.

提交回复
热议问题