JavaFX - ComboBox con cuadro de búsqueda
Un ComboBox es un control JavaFX que muestra una lista desplegable de elementos, el usuario puede seleccionar el elemento deseado, cuando la lista es extensa el usuario puede tener problemas con la búsqueda, por lo que intentaremos crear un ComboBox JavaFX con un cuadro de búsqueda.
Para lograr este objetivo extenderemos la clase javafx.scene.control.ComboBox para agregar una lista filtrada según en texto contenido en el javafx.scene.control.TextField, también es necesario redefinir el Skin, para un ComboBox la clase ComboBoxListViewSkin define la apariencia y comportamiento del control, extenderemos esta clase para agregar el cuadro de búsqueda y el filtro.
public class SearchComboBoxSkin<T> extends ComboBoxListViewSkin<T> { private final TextField searchBox; private final ListView<T> itemView; private boolean clickSelection = false; public SearchComboBoxSkin(SearchComboBox<T> comboBox) { super(comboBox); searchBox = new TextField(); searchBox.setPromptText("Search Box"); searchBox.textProperty().addListener((p, o, text) -> handleTextChange(text)); itemView = new ListView<>(); itemView.setItems(comboBox.getFilterList()); // administrar la seleccion de un nuevo item itemView.getSelectionModel().selectedItemProperty().addListener((p, o, item) -> { if (item != null) { comboBox.getSelectionModel().select(item); // ocultar popup cuando el item fue seleccionado mediante un click if (clickSelection) { comboBox.hide(); } } }); // ocultar popup al usar las teclas determindas ENTER, ESC, SPACE itemView.setOnKeyPressed(t -> { if (t.getCode() == KeyCode.ENTER || t.getCode() == KeyCode.SPACE || t.getCode() == KeyCode.ESCAPE) { comboBox.hide(); } }); // cambia el foco del TextField al ListView usando las teclas ENTER y ESC searchBox.setOnKeyPressed(t -> { if (t.getCode() == KeyCode.ENTER || t.getCode() == KeyCode.ESCAPE) { itemView.requestFocus(); } }); // se ha hecho click sobre el ListView itemView.addEventFilter(MouseEvent.ANY, me -> clickSelection = me.getEventType().equals(MouseEvent.MOUSE_PRESSED)); } @Override protected PopupControl getPopup() { // redefinir el combobox popup super.getPopup().setSkin(new Skin<Skinnable>() { @Override public Skinnable getSkinnable() { return null; } @Override public Node getNode() { return createPopupContent(); } @Override public void dispose() { } }); return super.getPopup(); } private void handleTextChange(String text) { SearchComboBox<T> scb = ((SearchComboBox) getSkinnable()); scb.setPredicateFilter(item -> text.isEmpty() ? true : scb.getFilter().test(item, text)); } private Node createPopupContent() { VBox box = new VBox(searchBox, itemView); box.setSpacing(2.0); box.setPadding(new Insets(2.0)); box.getStyleClass().add("combo-box-popup"); box.setMaxWidth(getSkinnable().getWidth()); return box; } @Override protected void handleControlPropertyChanged(String p) { super.handleControlPropertyChanged(p); if ("SHOWING".equals(p)) { SearchComboBox<T> scb = ((SearchComboBox<T>) getSkinnable()); if (scb.isShowing()) { searchBox.clear(); itemView.getSelectionModel().select(scb.getValue()); itemView.requestFocus(); } } } }
Sobre escribimos el método protected PopupControl getPopup() este es el encargado de crear el Popup del ComboBox, el Popup es la ventana que se muestra al hacer clic sobre el control, esta ventana le permite al usuario seleccionar un elemento de la lista disponible.
Hacemos la modificaciones necesarias para mostrar un cuadro de texto y una nueva lista que contendrá solamente los elementos que coinciden con el filtro de búsqueda.
public class SearchComboBoxSkin<T> extends ComboBoxListViewSkin<T> { @Override protected PopupControl getPopup() { // redefinir el combobox popup super.getPopup().setSkin(new Skin<Skinnable>() { @Override public Skinnable getSkinnable() { return null; } @Override public Node getNode() { return createPopupContent(); } @Override public void dispose() { } }); return super.getPopup(); } private Node createPopupContent() { VBox box = new VBox(searchBox, itemView); box.setSpacing(2.0); box.setPadding(new Insets(2.0)); box.getStyleClass().add("combo-box-popup"); box.setMaxWidth(getSkinnable().getWidth()); return box; } }
Cuando es usuario escriba un texto en el cuadro de búsqueda, debemos cambiar el filtro para mostrar solo aquellos elementos que coincidan con el mismo, agregamos un listener a la propiedad textProperty para reaccionar a la introducción de un nuevo texto, cuando ello ocurre cambiamos el filtro.
public class SearchComboBoxSkin<T> extends ComboBoxListViewSkin<T> { public SearchComboBoxSkin(SearchComboBox<T> comboBox) { super(comboBox); // cuadro de busqueda searchBox = new TextField(); searchBox.setPromptText("Search Box"); searchBox.textProperty().addListener((p, o, text) -> handleTextChange(text)); // ListView que muestra los elementos filtrados itemView = new ListView<>(); itemView.setItems(comboBox.getFilterList()); } private void handleTextChange(String text) { SearchComboBox<T> scb = ((SearchComboBox) getSkinnable()); scb.setPredicateFilter(item -> text.isEmpty() ? true : scb.getFilter().test(item, text)); } }
Extendemos la clase ComboBox<T> para agregar un FilteredList<T> (lista filtrado de elementos de tipo T) y un BiPredicate<T, String> que nos permite definir el criterio de búsqueda, donde T es el elemento de la lista y String el texto del cuadro de búsqueda.
public class SearchComboBox<T> extends ComboBox<T> { private FilteredList<T> filterList; private BiPredicate<T, String> filter; public SearchComboBox() { this(FXCollections.observableArrayList()); } public SearchComboBox(ObservableList<T> items) { this.filterList = new FilteredList<>(items); this.filter = (i, s) -> true; super.setItems(items); super.itemsProperty().addListener((p, o, n) -> { this.filterList = new FilteredList<>(n); }); } @Override protected Skin<?> createDefaultSkin() { return new SearchComboBoxSkin<>(this); } public void setFilter(BiPredicate<T, String> filter) { this.filter = filter; } public BiPredicate<T, String> getFilter() { return filter; } public void setPredicateFilter(Predicate<T> predicate) { filterList.setPredicate(predicate); } public FilteredList<T> getFilterList() { return filterList; } }
Utilizamos nuestro control de la siguiente manera, creamos la lista de elementos, para este ejemplo usamos un conjunto de cadenas de texto, pueden ser elementos de cualquier tipo, establecemos la lista usando el método setItems(items), y finalmente indicamos el filtro, usaremos un filtro sencillo, todos los elementos que contengan el texto escrito serán mostrados, aplicamos el filtro usando setFilter(filter).
SearchComboBox<String> cbx = new SearchComboBox<>(); cbx.setItems(items); cbx.setFilter((item, text) -> item.contains(text)); cbx.setPrefWidth(250.0); cbx.getSelectionModel().select(5);
Con ello logramos lo siguiente:
GitHub: ComboBox con Cuadro de Búsqueda
Comentarios
Publicar un comentario