domingo, 6 de março de 2016

Simplest JavaFX ComboBox autocomplete

Based on this Brazilian community post, I've created a sample Combobox auto complete. What it basically does is:

  • When user type with the combobox selected, it will work on a temporary string to store the typed text;
  • Each key typed leads to the combobox to be showed and updated
  • If backspace is type, we update the filter
  • Each key typed shows the combo box items, when the combobox is hidden, the filter is cleaned and the tooltip is hidden:


 



The class code and a sample application is below. I also added the source to my personal github, sent me PR to improve it and there are a lot of things to improve, like space and accents support.


2 comentários:

  1. Little bugfix (in your simple example tootlip position it's OK, but in other complex cases it isn`t):

    double posX = stage.getX() + comboBox.localToScene(comboBox.getBoundsInLocal()).getMinX();
    double posY = stage.getY() + comboBox.localToScene(comboBox.getBoundsInLocal()).getMinY();

    It runs very good! Congrats :)

    ResponderExcluir
  2. My solution, which fixes some bugs:

    import java.util.List;
    import java.util.stream.Collectors;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.event.Event;
    import javafx.scene.control.ComboBox;
    import javafx.scene.control.Tooltip;
    import javafx.scene.input.KeyCode;
    import javafx.scene.input.KeyEvent;
    import javafx.stage.Window;
    import org.apache.commons.lang3.StringUtils;

    public class ComboBoxAutoComplete extends ComboBox {

    private ObservableList originalItems;
    private StringProperty filter = new SimpleStringProperty(StringUtils.EMPTY);
    private AutoCompleteComparator comparator = (typedText, objectToCompare) -> false;

    public interface AutoCompleteComparator {
    boolean matches(String typedText, T objectToCompare);
    }

    public void initialize(AutoCompleteComparator comparator) {
    this.comparator = comparator;
    this.originalItems = FXCollections.observableArrayList(getItems());

    setTooltip(new Tooltip());
    getTooltip().textProperty().bind(filter);

    filter.addListener((observable, oldValue, newValue) -> handleFilterChanged(newValue));

    setOnKeyPressed(this::handleOnKeyPressed);
    setOnHidden(this::handleOnHiding);
    }

    private void handleOnKeyPressed(KeyEvent keyEvent) {
    KeyCode code = keyEvent.getCode();
    String filterValue = filter.get();
    if (code.isLetterKey()) {
    filterValue += keyEvent.getText();
    } else if (code == KeyCode.BACK_SPACE && filterValue.length() > 0) {
    filterValue = filterValue.substring(0, filterValue.length() - 1);
    } else if (code == KeyCode.ESCAPE) {
    filterValue = StringUtils.EMPTY;
    } else if (code == KeyCode.DOWN || code == KeyCode.UP) {
    show();
    }
    filter.setValue(filterValue);
    }

    private void handleFilterChanged(String newValue) {
    if (StringUtils.isNoneBlank(newValue)) {
    show();
    if (StringUtils.isBlank(filter.get())) {
    restoreOriginalItems();
    } else {
    showTooltip();
    getItems().setAll(filterItems());
    }
    } else {
    getTooltip().hide();
    }
    }

    private void showTooltip() {
    if (!getTooltip().isShowing()) {
    Window stage = getScene().getWindow();
    double posX = stage.getX() + localToScene(getBoundsInLocal()).getMinX() + 4;
    double posY = stage.getY() + localToScene(getBoundsInLocal()).getMinY() - 29;
    getTooltip().show(stage, posX, posY);
    }
    }

    private ObservableList filterItems() {
    List filteredList = originalItems.stream().filter(el -> comparator.matches(filter.get().toLowerCase(), el)).collect(Collectors.toList());
    return FXCollections.observableArrayList(filteredList);
    }

    private void handleOnHiding(Event e) {
    filter.setValue(StringUtils.EMPTY);
    getTooltip().hide();
    restoreOriginalItems();
    }

    private void restoreOriginalItems() {
    T s = getSelectionModel().getSelectedItem();
    getItems().setAll(originalItems);
    getSelectionModel().select(s);
    }

    }

    ResponderExcluir