更改 ComboBox 的项目而不更改 ValueProperty

编辑:我试图构建一个带有搜索功能的组合框,这就是我想出的:


public class SearchableComboBox<T> extends ComboBox<T> {


private ObservableList<T> filteredItems;

private ObservableList<T> originalItems;

private T selectedItem;

private StringProperty filter = new SimpleStringProperty("");


public SearchableComboBox () {

    this.setTooltip(new Tooltip());

    this.setOnKeyPressed(this::handleOnKeyPressed);

    this.getTooltip().textProperty().bind(filter);


    this.showingProperty().addListener(new ChangeListener<Boolean>() {

        @Override

        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {

            // If user "closes" the ComboBox dropdown list: reset filter, reset list to the full original list, hide tooltip

            if (newValue == false) {

                filter.setValue("");;

                setItems(originalItems);

                getTooltip().hide();

            // If user opens the combobox dropdown list: get a copy of the items and show tooltip   

            } else {

                originalItems = getItems();

                Window stage = getScene().getWindow();

                getTooltip().show(stage);

            }

        }


    });


}


public void handleOnKeyPressed(KeyEvent e) {


    //Only execute if the dropdown list of the combobox is opened

    if (this.showingProperty().getValue() == true) {

        // Get key and add it to the filter string

        String c = e.getText();

        filter.setValue(filter.getValue() + c);

        //Filter out objects that dont contain the filter

        this.filteredItems = this.originalItems.filtered(a -> this.getConverter().toString(a).toLowerCase().contains(filter.getValue().toLowerCase()));

        //Set the items of the combox to the filtered list

        this.setItems(filteredItems);


    }


}

这个想法很简单:只要打开组合框的下拉列表,我就会监听按键并将字符添加到过滤器中。使用这些过滤器,组合框的项目列表被过滤为仅包含包含过滤字符串的项目的列表。然后我使用 setItems 将项目列表设置为我的过滤列表。我的问题是,组合框的 valueProperty 更改,但我希望所选对象保持不变,直到用户从下拉列表中选择另一个。


尚方宝剑之说
浏览 122回答 2
2回答

白板的微信

我的建议是FilteredList从您的原始列表中创建一个。然后,使用 aPredicate过滤掉不匹配的结果。如果您将ComboBox项目设置为该过滤列表,它将始终显示所有项目或与您的搜索词匹配的项目。将ValueProperty只会更新,当用户“提交”按变化[进入]。我在这里有一个简短的 MCVE 应用程序来演示整个注释:import javafx.application.Application;import javafx.collections.FXCollections;import javafx.collections.ObservableList;import javafx.collections.transformation.FilteredList;import javafx.geometry.Insets;import javafx.geometry.Pos;import javafx.scene.Scene;import javafx.scene.control.ComboBox;import javafx.scene.layout.VBox;import javafx.stage.Stage;public class Main extends Application {&nbsp; &nbsp; // Create a list of items&nbsp; &nbsp; private final ObservableList<String> items = FXCollections.observableArrayList();&nbsp; &nbsp; // Create the ComboBox&nbsp; &nbsp; private final ComboBox<String> comboBox = new ComboBox<>();&nbsp; &nbsp; public static void main(String[] args) {&nbsp; &nbsp; &nbsp; &nbsp; launch(args);&nbsp; &nbsp; }&nbsp; &nbsp; @Override&nbsp; &nbsp; public void start(Stage primaryStage) {&nbsp; &nbsp; &nbsp; &nbsp; // Simple Interface&nbsp; &nbsp; &nbsp; &nbsp; VBox root = new VBox(10);&nbsp; &nbsp; &nbsp; &nbsp; root.setAlignment(Pos.CENTER);&nbsp; &nbsp; &nbsp; &nbsp; root.setPadding(new Insets(10));&nbsp; &nbsp; &nbsp; &nbsp; // Allow manual entry into ComboBox&nbsp; &nbsp; &nbsp; &nbsp; comboBox.setEditable(true);&nbsp; &nbsp; &nbsp; &nbsp; // Add sample items to our list&nbsp; &nbsp; &nbsp; &nbsp; items.addAll("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten");&nbsp; &nbsp; &nbsp; &nbsp; createListener();&nbsp; &nbsp; &nbsp; &nbsp; root.getChildren().add(comboBox);&nbsp; &nbsp; &nbsp; &nbsp; // Show the stage&nbsp; &nbsp; &nbsp; &nbsp; primaryStage.setScene(new Scene(root));&nbsp; &nbsp; &nbsp; &nbsp; primaryStage.setTitle("Filtered ComboBox");&nbsp; &nbsp; &nbsp; &nbsp; primaryStage.show();&nbsp; &nbsp; }&nbsp; &nbsp; private void createListener() {&nbsp; &nbsp; &nbsp; &nbsp; // Create the listener to filter the list as user enters search terms&nbsp; &nbsp; &nbsp; &nbsp; FilteredList<String> filteredList = new FilteredList<>(items);&nbsp; &nbsp; &nbsp; &nbsp; // Add listener to our ComboBox textfield to filter the list&nbsp; &nbsp; &nbsp; &nbsp; comboBox.getEditor().textProperty().addListener((observable, oldValue, newValue) ->&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; filteredList.setPredicate(item -> {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If the TextField is empty, return all items in the original list&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (newValue == null || newValue.isEmpty()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Check if the search term is contained anywhere in our list&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (item.toLowerCase().contains(newValue.toLowerCase().trim())) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // No matches found&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }));&nbsp; &nbsp; &nbsp; &nbsp; // Finally, let's add the filtered list to our ComboBox&nbsp; &nbsp; &nbsp; &nbsp; comboBox.setItems(filteredList);&nbsp; &nbsp; }}您将拥有一个简单的、可编辑的 ComboBox,它可以从列表中过滤掉不匹配的值。使用这种方法,您不需要监听每个按键,但可以在其Predicate自身内提供任何过滤指令,如上所示。结果:编辑:ComboBox但是,可编辑项存在一些问题需要解决,因为从列表中选择一个项目会引发IndexOutOfBoundsException.这可以通过使用单独TextField的过滤器来缓解,但保持与上述基本相同的代码。而不是将侦听器添加到comboBox.getEditor(),只需将其更改为textField。这将毫无问题地过滤列表。这是使用该方法的完整 MCVE:import javafx.application.Application;import javafx.collections.FXCollections;import javafx.collections.ObservableList;import javafx.collections.transformation.FilteredList;import javafx.geometry.Insets;import javafx.geometry.Pos;import javafx.scene.Scene;import javafx.scene.control.ComboBox;import javafx.scene.control.TextField;import javafx.scene.layout.VBox;import javafx.stage.Stage;public class Main extends Application {&nbsp; &nbsp; // Create a list of items&nbsp; &nbsp; private final ObservableList<String> items = FXCollections.observableArrayList();&nbsp; &nbsp; // Create the search field&nbsp; &nbsp; TextField textField = new TextField("Filter ...");&nbsp; &nbsp; // Create the ComboBox&nbsp; &nbsp; private final ComboBox<String> comboBox = new ComboBox<>();&nbsp; &nbsp; public static void main(String[] args) {&nbsp; &nbsp; &nbsp; &nbsp; launch(args);&nbsp; &nbsp; }&nbsp; &nbsp; @Override&nbsp; &nbsp; public void start(Stage primaryStage) {&nbsp; &nbsp; &nbsp; &nbsp; // Simple Interface&nbsp; &nbsp; &nbsp; &nbsp; VBox root = new VBox(10);&nbsp; &nbsp; &nbsp; &nbsp; root.setAlignment(Pos.CENTER);&nbsp; &nbsp; &nbsp; &nbsp; root.setPadding(new Insets(10));&nbsp; &nbsp; &nbsp; &nbsp; // Add sample items to our list&nbsp; &nbsp; &nbsp; &nbsp; items.addAll("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten");&nbsp; &nbsp; &nbsp; &nbsp; createListener();&nbsp; &nbsp; &nbsp; &nbsp; root.getChildren().addAll(textField, comboBox);&nbsp; &nbsp; &nbsp; &nbsp; // Show the stage&nbsp; &nbsp; &nbsp; &nbsp; primaryStage.setScene(new Scene(root));&nbsp; &nbsp; &nbsp; &nbsp; primaryStage.setTitle("Filtered ComboBox");&nbsp; &nbsp; &nbsp; &nbsp; primaryStage.show();&nbsp; &nbsp; }&nbsp; &nbsp; private void createListener() {&nbsp; &nbsp; &nbsp; &nbsp; // Create the listener to filter the list as user enters search terms&nbsp; &nbsp; &nbsp; &nbsp; FilteredList<String> filteredList = new FilteredList<>(items);&nbsp; &nbsp; &nbsp; &nbsp; // Add listener to our ComboBox textfield to filter the list&nbsp; &nbsp; &nbsp; &nbsp; textField.textProperty().addListener((observable, oldValue, newValue) ->&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; filteredList.setPredicate(item -> {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If the TextField is empty, return all items in the original list&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (newValue == null || newValue.isEmpty()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Check if the search term is contained anywhere in our list&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (item.toLowerCase().contains(newValue.toLowerCase().trim())) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // No matches found&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }));&nbsp; &nbsp; &nbsp; &nbsp; // Finally, let's add the filtered list to our ComboBox&nbsp; &nbsp; &nbsp; &nbsp; comboBox.setItems(filteredList);&nbsp; &nbsp; &nbsp; &nbsp; // Allow the ComboBox to extend in size&nbsp; &nbsp; &nbsp; &nbsp; comboBox.setMaxWidth(Double.MAX_VALUE);&nbsp; &nbsp; }}

森栏

我发现的最佳解决方案是稍微修改的版本:JavaFX searchable combobox (like js select2)我修改的内容是使 InputFilter 类通用,并且组合框在关闭下拉列表后失去焦点。这里的代码:import javafx.application.Platform;import javafx.beans.property.SimpleStringProperty;import javafx.beans.property.StringProperty;import javafx.beans.value.ChangeListener;import javafx.beans.value.ObservableValue;import javafx.collections.transformation.FilteredList;import javafx.scene.control.ComboBox;public class InputFilter<T> implements ChangeListener<String> {private ComboBox<T> box;private FilteredList<T> items;private boolean upperCase;private int maxLength;private String restriction;private int count = 0;/**&nbsp;* @param box&nbsp;*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; The combo box to whose textProperty this listener is&nbsp;*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; added.&nbsp;* @param items&nbsp;*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; The {@link FilteredList} containing the items in the list.&nbsp;*/public InputFilter(ComboBox<T> box, FilteredList<T> items, boolean upperCase, int maxLength,&nbsp; &nbsp; &nbsp; &nbsp; String restriction) {&nbsp; &nbsp; this.box = box;&nbsp; &nbsp; this.items = items;&nbsp; &nbsp; this.upperCase = upperCase;&nbsp; &nbsp; this.maxLength = maxLength;&nbsp; &nbsp; this.restriction = restriction;&nbsp; &nbsp; this.box.setItems(items);&nbsp; &nbsp; this.box.showingProperty().addListener(new ChangeListener<Boolean>() {&nbsp; &nbsp; &nbsp; &nbsp; @Override&nbsp; &nbsp; &nbsp; &nbsp; public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (newValue == false) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; items.setPredicate(null);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; box.getParent().requestFocus();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; });}public InputFilter(ComboBox<T> box, FilteredList<T> items, boolean upperCase, int maxLength) {&nbsp; &nbsp; this(box, items, upperCase, maxLength, null);}public InputFilter(ComboBox<T> box, FilteredList<T> items, boolean upperCase) {&nbsp; &nbsp; this(box, items, upperCase, -1, null);}public InputFilter(ComboBox<T> box, FilteredList<T> items) {&nbsp; &nbsp; this(box, items, false);}@Overridepublic void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {&nbsp; &nbsp; StringProperty value = new SimpleStringProperty(newValue);&nbsp; &nbsp; this.count++;&nbsp; &nbsp; System.out.println(this.count);&nbsp; &nbsp; System.out.println(oldValue);&nbsp; &nbsp; System.out.println(newValue);&nbsp; &nbsp; // If any item is selected we save that reference.&nbsp; &nbsp; T selected = box.getSelectionModel().getSelectedItem() != null&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ? box.getSelectionModel().getSelectedItem() : null;&nbsp; &nbsp; String selectedString = null;&nbsp; &nbsp; // We save the String of the selected item.&nbsp; &nbsp; if (selected != null) {&nbsp; &nbsp; &nbsp; &nbsp; selectedString =&nbsp; this.box.getConverter().toString(selected);&nbsp; &nbsp; }&nbsp; &nbsp; if (upperCase) {&nbsp; &nbsp; &nbsp; &nbsp; value.set(value.get().toUpperCase());&nbsp; &nbsp; }&nbsp; &nbsp; if (maxLength >= 0 && value.get().length() > maxLength) {&nbsp; &nbsp; &nbsp; &nbsp; value.set(oldValue);&nbsp; &nbsp; }&nbsp; &nbsp; if (restriction != null) {&nbsp; &nbsp; &nbsp; &nbsp; if (!value.get().matches(restriction + "*")) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value.set(oldValue);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; // If an item is selected and the value in the editor is the same&nbsp; &nbsp; // as the selected item we don't filter the list.&nbsp; &nbsp; if (selected != null && value.get().equals(selectedString)) {&nbsp; &nbsp; &nbsp; &nbsp; // This will place the caret at the end of the string when&nbsp; &nbsp; &nbsp; &nbsp; // something is selected.&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(value.get());&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(selectedString);&nbsp; &nbsp; &nbsp; &nbsp; Platform.runLater(() -> box.getEditor().end());&nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; items.setPredicate(item -> {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println("setPredicate");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println(value.get());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T itemString = item;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (this.box.getConverter().toString(itemString).toUpperCase().contains(value.get().toUpperCase())) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; }&nbsp; &nbsp; // If the popup isn't already showing we show it.&nbsp; &nbsp; if (!box.isShowing()) {&nbsp; &nbsp; &nbsp; &nbsp; // If the new value is empty we don't want to show the popup,&nbsp; &nbsp; &nbsp; &nbsp; // since&nbsp; &nbsp; &nbsp; &nbsp; // this will happen when the combo box gets manually reset.&nbsp; &nbsp; &nbsp; &nbsp; if (!newValue.isEmpty() && box.isFocused()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; box.show();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; // If it is showing and there's only one item in the popup, which is&nbsp; &nbsp; // an&nbsp; &nbsp; // exact match to the text, we hide the dropdown.&nbsp; &nbsp; else {&nbsp; &nbsp; &nbsp; &nbsp; if (items.size() == 1) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // We need to get the String differently depending on the&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // nature&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // of the object.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T item = items.get(0);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // To get the value we want to compare with the written&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // value, we need to crop the value according to the current&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // selectionCrop.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T comparableItem = item;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (value.get().equals(comparableItem)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Platform.runLater(() -> box.hide());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; box.getEditor().setText(value.get());}}然后将 InputFilter 添加为 Combobox 的 textField 的更改侦听器:comboBox.getEditor().textProperty().addListener(new InputFilter<YourCustomClass>(comboBox, new FilteredList<YourCustomClass>(comboBox.getItems())));目前Combobox.setEditable(true)必须在外部手动完成,但我计划将其移入 InputFilter 本身。您还需要为 Combobox 设置一个字符串转换器。到目前为止,这个解决方案对我来说很糟糕,唯一缺少的是在输入搜索键时支持空格键。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java