Java 在不更改ValueProperty的情况下更改ComboBox的项

Java 在不更改ValueProperty的情况下更改ComboBox的项,java,javafx,combobox,listener,Java,Javafx,Combobox,Listener,编辑: 我正在尝试构建一个带有搜索功能的组合框,这就是我想到的: public class SearchableComboBox<T> extends ComboBox<T> { private ObservableList<T> filteredItems; private ObservableList<T> originalItems; private T selectedItem; private StringProperty filter

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

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);

    }

}
公共类SearchableComboBox扩展了ComboBox{
私人可观察过滤器;
私人观察主义原创;
私人T-selectedItem;
私有StringProperty筛选器=新的SimpleStringProperty(“”);
公共可搜索组合框(){
this.setTooltip(新工具提示());
this.setOnKeyPressed(this::handleOnKeyPressed);
this.getTooltip().textProperty().bind(过滤器);
this.showingProperty().addListener(新的ChangeListener()){
@凌驾

public void changed(observevalue我建议从原始列表创建一个
FilteredList
。然后,使用
谓词
过滤掉不匹配的结果。如果您将
组合框
项目设置为该过滤列表,它将始终显示所有项目或与搜索条件匹配的项目

只有当用户按[enter]键“提交”更改时,
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);

    }
}

我的建议是从原始列表中创建一个
FilteredList
。然后,使用
谓词
过滤掉不匹配的结果。如果您将
组合框
项目设置为该过滤列表,它将始终显示所有项目或与搜索条件匹配的项目

只有当用户按[enter]键“提交”更改时,
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);

    }
}

我找到的最佳解决方案是对以下版本稍加修改:

我修改的内容是使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());
}
导入javafx.application.Platform;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.beans.property.StringProperty;
导入javafx.beans.value.ChangeListener;
导入javafx.beans.value.observeValue;
导入javafx.collections.transformation.FilteredList;
导入javafx.scene.control.ComboBox;
公共类InputFilter实现ChangeListener{
私人组合框;
私人筛选列表项目;
私有布尔大写;
私有整数最大长度;
私有字符串限制;
私有整数计数=0;
/**
*@param-box
*此侦听器指向其textProperty的组合框
*补充说。
*@param项目
*包含列表中项目的{@link FilteredList}。
*/
公共InputFilter(组合框、FilteredList项、布尔大写、int maxLength、,
字符串限制){
this.box=box;
这个项目=项目;
this.upperCase=大写;
this.maxLength=maxLength;
这个限制=限制;
此.box.setItems(项目);
this.box.showingProperty().addListener(新的ChangeListener()){
@凌驾

public void changed(observevalue我发现的最佳解决方案是对以下内容稍加修改:

我修改的内容是使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());
}
导入javafx.application.Platform;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.beans.property.StringProperty;
导入javafx.beans.value.ChangeListener;
导入javafx.beans.value.observeValue;
导入javafx.collections.transformation.FilteredList;
导入javafx.scene.control.ComboBox;
公共类InputFilter实现ChangeListener{
私人组合框;
私人筛选列表项目;
私有布尔大写;
私有整数最大长度;
私有字符串限制;
私有整数计数=0;
/**
*@param-box
*此侦听器指向其textProperty的组合框
*补充说。
*@param项目
*包含列表中项目的{@link FilteredList}。
*/
公共InputFilter(组合框、FilteredList项、布尔大写、int maxLength、,
字符串限制){
this.box=box;
这个项目=项目;
this.upperCase=大写;
this.maxLength=maxLength;
这个限制=限制;
此.box.setItems(项目);
this.box.showingProperty().addListener(新的ChangeListener()){
@凌驾

public void已更改(observevable值我在家时一定会这样做。但到目前为止,可编辑性对我来说是一个相当大的问题。每次我设置可编辑性(true)我的StringConverter中出现了一个nullpointerexception。我的解决方案和您的解决方案之间唯一的真正区别是您使用的是editable而不是Im,那么为什么在您的案例中以及在我的代码中,该值都没有改变?(请注意,即使对setValue部分进行了注释,该值在我的案例中仍然会改变)老实说,我不完全了解你的代码在做什么,如果没有完整的代码供我自己测试,我就无法真正复制你的问题。但请尝试一下我的解决方案,看看它是否满足你的需要;否则,我恐怕我不完全确定你的目标是什么。你真的不需要完整的代码,你可以粘贴我的SearchableCombo因为这是一个独立的类(虽然由于缺少导入而需要进行一些单击,并且addListener一开始可能无法被ide识别)。我尝试了your代码,但有一件事我注意到,当选择高于“1”的任何内容时,通常会出现IndexOutOfBoundsException。此外,在选择一个项目后,您必须手动从文本框中删除该项目,因为它是用于筛选的新谓词。正如我所说,到目前为止,我无法获得setEditable(true)工作,但在我的应用程序中,我不是使用简单的字符串,而是使用自定义类,仍然不知道为什么它不工作。我会编辑这个问题,希望我能用这种方式说得更清楚一点。谢谢你的努力,但现在请你这么说
comboBox.getEditor().textProperty().addListener(new InputFilter<YourCustomClass>(comboBox, new FilteredList<YourCustomClass>(comboBox.getItems())));