为了账号安全,请及时绑定邮箱和手机立即绑定

更改 ComboBox 的项目而不更改 ValueProperty

更改 ComboBox 的项目而不更改 ValueProperty

尚方宝剑之说 2021-12-30 20:41:16
编辑:我试图构建一个带有搜索功能的组合框,这就是我想出的: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 更改,但我希望所选对象保持不变,直到用户从下拉列表中选择另一个。
查看完整描述

2 回答

?
白板的微信

TA贡献1883条经验 获得超3个赞

我的建议是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 {


    // Create a list of items

    private final ObservableList<String> items = FXCollections.observableArrayList();


    // Create the ComboBox

    private final ComboBox<String> comboBox = new ComboBox<>();


    public static void main(String[] args) {

        launch(args);

    }


    @Override

    public void start(Stage primaryStage) {


        // Simple Interface

        VBox root = new VBox(10);

        root.setAlignment(Pos.CENTER);

        root.setPadding(new Insets(10));


        // Allow manual entry into ComboBox

        comboBox.setEditable(true);


        // Add sample items to our list

        items.addAll("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten");


        createListener();


        root.getChildren().add(comboBox);


        // Show the stage

        primaryStage.setScene(new Scene(root));

        primaryStage.setTitle("Filtered ComboBox");

        primaryStage.show();

    }


    private void createListener() {


        // Create the listener to filter the list as user enters search terms

        FilteredList<String> filteredList = new FilteredList<>(items);


        // Add listener to our ComboBox textfield to filter the list

        comboBox.getEditor().textProperty().addListener((observable, oldValue, newValue) ->

                filteredList.setPredicate(item -> {


                    // If the TextField is empty, return all items in the original list

                    if (newValue == null || newValue.isEmpty()) {

                        return true;

                    }


                    // Check if the search term is contained anywhere in our list

                    if (item.toLowerCase().contains(newValue.toLowerCase().trim())) {

                        return true;

                    }


                    // No matches found

                    return false;

                }));


        // Finally, let's add the filtered list to our ComboBox

        comboBox.setItems(filteredList);


    }

}

您将拥有一个简单的、可编辑的 ComboBox,它可以从列表中过滤掉不匹配的值。


使用这种方法,您不需要监听每个按键,但可以在其Predicate自身内提供任何过滤指令,如上所示。


结果:

//img1.sycdn.imooc.com//61cda9160001c01803190390.jpg//img1.sycdn.imooc.com//61cda91c00016b1203190212.jpg

编辑:


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 {


    // Create a list of items

    private final ObservableList<String> items = FXCollections.observableArrayList();


    // Create the search field

    TextField textField = new TextField("Filter ...");


    // Create the ComboBox

    private final ComboBox<String> comboBox = new ComboBox<>();


    public static void main(String[] args) {

        launch(args);

    }


    @Override

    public void start(Stage primaryStage) {


        // Simple Interface

        VBox root = new VBox(10);

        root.setAlignment(Pos.CENTER);

        root.setPadding(new Insets(10));


        // Add sample items to our list

        items.addAll("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten");


        createListener();


        root.getChildren().addAll(textField, comboBox);


        // Show the stage

        primaryStage.setScene(new Scene(root));

        primaryStage.setTitle("Filtered ComboBox");

        primaryStage.show();

    }


    private void createListener() {


        // Create the listener to filter the list as user enters search terms

        FilteredList<String> filteredList = new FilteredList<>(items);


        // Add listener to our ComboBox textfield to filter the list

        textField.textProperty().addListener((observable, oldValue, newValue) ->

                filteredList.setPredicate(item -> {


                    // If the TextField is empty, return all items in the original list

                    if (newValue == null || newValue.isEmpty()) {

                        return true;

                    }


                    // Check if the search term is contained anywhere in our list

                    if (item.toLowerCase().contains(newValue.toLowerCase().trim())) {

                        return true;

                    }


                    // No matches found

                    return false;

                }));


        // Finally, let's add the filtered list to our ComboBox

        comboBox.setItems(filteredList);


        // Allow the ComboBox to extend in size

        comboBox.setMaxWidth(Double.MAX_VALUE);


    }

}

//img1.sycdn.imooc.com//61cda93100017d6302270199.jpg

查看完整回答
反对 回复 2021-12-30
?
森栏

TA贡献1810条经验 获得超5个赞

我发现的最佳解决方案是稍微修改的版本: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;


/**

 * @param box

 *            The combo box to whose textProperty this listener is

 *            added.

 * @param items

 *            The {@link FilteredList} containing the items in the list.

 */

public InputFilter(ComboBox<T> box, FilteredList<T> items, boolean upperCase, int maxLength,

        String restriction) {

    this.box = box;

    this.items = items;

    this.upperCase = upperCase;

    this.maxLength = maxLength;

    this.restriction = restriction;

    this.box.setItems(items);

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


        @Override

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

            if (newValue == false) {

                items.setPredicate(null);

                box.getParent().requestFocus();

            }


        }


    });

}


public InputFilter(ComboBox<T> box, FilteredList<T> items, boolean upperCase, int maxLength) {

    this(box, items, upperCase, maxLength, null);

}


public InputFilter(ComboBox<T> box, FilteredList<T> items, boolean upperCase) {

    this(box, items, upperCase, -1, null);

}


public InputFilter(ComboBox<T> box, FilteredList<T> items) {

    this(box, items, false);

}


@Override

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

    StringProperty value = new SimpleStringProperty(newValue);

    this.count++;

    System.out.println(this.count);

    System.out.println(oldValue);

    System.out.println(newValue);

    // If any item is selected we save that reference.

    T selected = box.getSelectionModel().getSelectedItem() != null

            ? box.getSelectionModel().getSelectedItem() : null;


    String selectedString = null;

    // We save the String of the selected item.

    if (selected != null) {

        selectedString =  this.box.getConverter().toString(selected);

    }


    if (upperCase) {

        value.set(value.get().toUpperCase());

    }


    if (maxLength >= 0 && value.get().length() > maxLength) {

        value.set(oldValue);

    }


    if (restriction != null) {

        if (!value.get().matches(restriction + "*")) {

            value.set(oldValue);

        }

    }


    // If an item is selected and the value in the editor is the same

    // as the selected item we don't filter the list.

    if (selected != null && value.get().equals(selectedString)) {

        // This will place the caret at the end of the string when

        // something is selected.

        System.out.println(value.get());

        System.out.println(selectedString);

        Platform.runLater(() -> box.getEditor().end());

    } else {

        items.setPredicate(item -> {

            System.out.println("setPredicate");

            System.out.println(value.get());

            T itemString = item;

            if (this.box.getConverter().toString(itemString).toUpperCase().contains(value.get().toUpperCase())) {

                return true;

            } else {

                return false;

            }

        });

    }


    // If the popup isn't already showing we show it.

    if (!box.isShowing()) {

        // If the new value is empty we don't want to show the popup,

        // since

        // this will happen when the combo box gets manually reset.

        if (!newValue.isEmpty() && box.isFocused()) {

            box.show();

        }

    }

    // If it is showing and there's only one item in the popup, which is

    // an

    // exact match to the text, we hide the dropdown.

    else {

        if (items.size() == 1) {

            // We need to get the String differently depending on the

            // nature

            // of the object.

            T item = items.get(0);


            // To get the value we want to compare with the written

            // value, we need to crop the value according to the current

            // selectionCrop.

            T comparableItem = item;


            if (value.get().equals(comparableItem)) {

                Platform.runLater(() -> box.hide());

            }

        }

    }


    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 设置一个字符串转换器。到目前为止,这个解决方案对我来说很糟糕,唯一缺少的是在输入搜索键时支持空格键。


查看完整回答
反对 回复 2021-12-30
  • 2 回答
  • 0 关注
  • 121 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信