更改ComboBox的项目而不更改ValueProperty

时间:2018-11-05 17:32:38

标签: 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 = 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种情况正在发生。首先是我打开下拉列表,不选择一个项目并输入我的过滤器。然后我选择一个项目,然后我的照片将向我显示:

值已更改

安德森(Andebsen),威布(Wiebke)

值已更改

值已更改

安德森(Andebsen),威布(Wiebke)

第二种情况是我打开下拉列表并选择一个项目。我现在继续输入过滤器,并且所选项目包含该过滤器。我的照片将向我展示:

值已更改

值已更改

安德森(Andebsen),威布(Wiebke)

每次选择安德森(Andersen)时,每次按下一个键,都再次Wiebke /关闭下拉列表。

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

值已更改

Karzi Budziszewski

值已更改

值已更改

Karzi Budziszewski

我想要的是ValueProperty不会更改,直到用户从下拉列表中选择一个新项目。我也很想知道为什么价值属性对我一直都会变化。特别是因为我并不真正认为我的解决方案与Zephyr提供的解决方案之间存在根本差异。我们都使用过滤字符串过滤原始列表,然后使用setItems()将组合框的列表设置为新过滤的列表。正如下面的评论中所述,我什至不能使用他的解决方案,因为我无法使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();

2 个答案:

答案 0 :(得分:2)

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

ValueProperty仅在用户通过按[enter]“提交”更改时更新。

我在这里有一个简短的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);

    }
}

您将拥有一个简单的可编辑组合框,该组合框从列表中过滤出不匹配的值。


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


  

结果:

screenshot

screenshot

  

编辑:

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

    }
}

screenshot

答案 1 :(得分:0)

我发现的最佳解决方案是稍作修改的版本: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添加为组合框的textField的changeListener:

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

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