【问题标题】:Changing Items of ComboBox without changing the ValueProperty在不更改 ValueProperty 的情况下更改 ComboBox 的项目
【发布时间】:2018-11-05 17:32:37
【问题描述】:

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

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 发生了变化,但我希望所选对象保持不变,直到用户从下拉列表中选择另一个对象。我在 ValueProperty 中添加了一个 ChangeListener:

public void changed(ObservableValue<? extends PersonalModel> observable, PersonalModel oldValue,
                PersonalModel newValue) {
            System.out.println("Value changed");
            if (newValue == null) {
                System.out.println(newValue);
            } else {
                System.out.println(personalauswahl.getConverter().toString(newValue));
                labelArbeitszeitAnzeige.setText(String.valueOf(newValue.getArbeitszeit()));
            }
        }

    });

当值改变时,控制台看起来像这样:

值改变了

Andersen, Wiebke(对象的字符串表示)

或者像这样:

值改变了

null(对象为null)

基本上有 3 种情况正在发生。首先是我打开下拉列表,不要选择一个项目并输入我的过滤器。然后我选择一个项目,我的打印件会显示给我:

值改变了

安徒生,维布克

值改变了

值改变了

安徒生,维布克

第二种情况是我打开下拉列表并选择一个项目。我现在继续输入过滤器,所选项目包含过滤器。我的照片会告诉我这个:

值改变了

值改变了

安徒生,维布克

每次我按下一个键,当我选择 Andersen 时,Wiebke 再次/关闭下拉列表。

第三种情况是选择一个项目,然后继续输入所选项目不包含的过滤器。一旦所选项目不再包含过滤器,valueProperty 的值就会更改为 null。如果我选择一个新项目,那么我会得到这个:

值改变了

Budziszewski,卡琳

值改变了

值改变了

Budziszewski,卡琳

我想要的是在用户从下拉列表中选择一个新项目之前,ValueProperty 不会改变。此外,我真的很想知道为什么 valueproperty 对我来说一直在变化。特别是因为我真的不认为我的解决方案与 Zephyr 提供的解决方案之间存在根本区别。我们都使用过滤器字符串过滤原始列表,然后使用 setItems() 将 Combobox 的列表设置为新过滤的列表。正如下面评论中提到的,我什至不能使用他的解决方案,因为我无法让 Combobox 的 setEditable 工作:

当我尝试 personalauswahl.setEditable(true);我得到原因:de.statistik_nord.klr.controller.EingabeController$1.toString(EingabeController.java:93) 的 java.lang.NullPointerException de.statistik_nord.klr.controller.EingabeController$1.toString(EingabeController.java:1)指向这行代码:return object.getName() +", " + object.getVorname();

【问题讨论】:

  • 只是一个评论:如果您除了配置之外什么都不做,就没有理由扩展组合 - 所以不要
  • 没有吗?如果我想在我的项目中多次使用它,我每次都必须进行配置,不是吗?这样我就可以实例化我的类而不是组合框并完成它?您将如何将其用于多次使用?
  • 您将配置添加到返回配置的ComboBox 的方法中。将该方法公开,您现在就有了一个可重复使用的ComboBox,而无需扩展它。

标签: java javafx combobox listener


【解决方案1】:

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

ValueProperty 只会在用户按 [enter] 键“提交”更改时更新。

我在这里有一个简短的 MCVE 应用程序来演示整个过程中的 cmets:

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 本身内提供任何过滤指令,如上所示。


结果:

编辑:

然而,可编辑的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);

    }
}

【讨论】:

  • 我一定会在我回家的时候尝试一下。但到目前为止,可编辑对我来说是一个相当大的问题。每次我执行 setEditable(true) 时,我的 StringConverter 都会出现 nullpointerexception。另外,我和您的解决方案之间唯一真正的区别是您使用的是可编辑的,而我没有,那么为什么值在您的情况和我的代码中没有改变呢? (请注意,即使 setValue 部分被注释,我的值仍然会发生变化)
  • 老实说,我不完全理解您的代码在做什么,并且没有我自己测试的整个代码,我无法真正复制您的问题。但是试试我的解决方案,看看它是否满足您的需求;否则,恐怕我不能完全确定你的目标是什么。
  • 您并不需要整个代码,您只需将我的 SearchableComboBox 粘贴到您的项目中并使用它,因为那是一个独立的类(由于缺少导入和 addListener 需要单击几下一开始可能不会被 ide 识别)。我尝试了你的代码,我注意到一件事在选择高于“一”的任何东西时确实经常出现 IndexOutOfBoundsException。此外,在选择一个项目后,您必须手动从文本框中删除该项目,因为它是用于过滤的新谓词。
  • 正如我所说,到目前为止我无法让 setEditable(true) 工作,但在我的应用程序中我没有使用简单的字符串,而是使用自定义类,仍然不知道为什么它不起作用.我会编辑这个问题,希望我能说得更清楚一点。感谢您的努力,但不幸的是,现在您的解决方案并没有真正让我有所收获,因为我什至无法使用 setEditable 来工作。
  • 当我尝试personalauswahl.setEditable(true);我得到原因:de.statistik_nord.klr.controller.EingabeController$1.toString(EingabeController.java:93) 的 java.lang.NullPointerException de.statistik_nord.klr.controller.EingabeController$1.toString(EingabeController.java:1)指向这行代码:return object.getName() +", " + object.getVorname();
【解决方案2】:

我找到的最好的解决方案是稍微修改一下这个版本:JavaFX searchable combobox (like js select2)

我修改的东西是使 InputFilter 类通用,并且 Combobox 在关闭下拉列表后失去焦点。代码如下:

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 的 changeListener:

comboBox.getEditor().textProperty().addListener(new InputFilter<YourCustomClass>(comboBox, new FilteredList<YourCustomClass>(comboBox.getItems())));

目前Combobox.setEditable(true) 必须在外部手动完成,但我计划将其移至 InputFilter 本身。您还需要为组合框设置一个字符串转换器。到目前为止,这个解决方案对我来说非常有用,唯一缺少的是在输入搜索键时支持空格键。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-09
    • 1970-01-01
    • 1970-01-01
    • 2014-05-12
    • 2012-06-02
    相关资源
    最近更新 更多