Изменение элементов ComboBox без изменения ValueProperty - PullRequest
0 голосов
/ 05 ноября 2018

Edit: Я пытаюсь создать Combobox с функцией поиска, и вот что я придумал:

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

    }

}

Идея проста: пока открыт раскрывающийся список Combobox, я слушаю нажатия клавиш и добавляю символы в фильтр. С помощью этих фильтров список элементов Combobox фильтруется в список, содержащий только те элементы, которые содержат строку фильтра. Затем я использую setItems, чтобы установить список элементов в мой отфильтрованный список. Моя проблема в том, что valueProperty Combobox изменяется, но я хочу, чтобы выбранный объект оставался прежним, пока пользователь не выберет другой из выпадающего списка. Я добавил ChangeListener в ValueProperty:

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

    });

Когда значение изменяется, консоль выглядит следующим образом:

Значение изменено

Андерсен, Вибке (Строковое представление объекта)

или как это:

Значение изменено

ноль (объект ноль)

Есть в основном 3 случая, которые происходят. Во-первых, я открываю выпадающий список, не выбираю элемент и не набираю фильтр. Затем я выбираю предмет, и мои отпечатки покажут мне это:

Значение изменено

Андерсен, Вибке

Значение изменено

нуль

Значение изменено

Андерсен, Вибке

Во втором случае я открываю выпадающий список и выбираю предмет. Теперь я перехожу к типу фильтра, и выбранный элемент содержит фильтр. Мои отпечатки покажут мне это:

Значение изменено

1037 * пустой *

Значение изменено

Андерсен, Вибке

Каждый раз, когда я нажимаю клавишу и снова, когда выбираю Андерсена, Вибке снова / закрывает выпадающий список.

Третий случай - выбор элемента, а затем приступить к вводу фильтра, который не содержит выбранный элемент. Как только выбранный Item больше не содержит фильтра, значение valueProperty изменяется на null. Если я выберу новый предмет, я получу это:

Значение изменено

Будзишевски, Карин

Значение изменено

1053 * пустой *

Значение изменено

Будзишевски, Карин

Я хочу, чтобы ValueProperty не менялся, пока пользователь не выберет новый элемент из выпадающего списка. Также мне бы очень хотелось узнать, почему именно значение свойства все время меняется для меня. Особенно потому, что я не думаю, что есть принципиальная разница между моим решением и тем, которое предоставил Зефир. Мы оба фильтруем исходный список с помощью строки фильтра и затем используем setItems (), чтобы установить список Combobox на вновь отфильтрованный. И, как упоминалось в комментарии ниже, я даже не могу использовать его решение », потому что я не могу заставить setEditable из Combobox работать:

Когда я пытаюсь использовать personalauswahl.setEditable (true); Вызывается: java.lang.NullPointerException в de.statistik_nord.klr.controller.EingabeController $ 1.toString (EingabeController.java:93) в de.statistik_nord.klr.controller.EingabeController который указывает на эту строку кода: return object.getName () + "," + object.getVorname ();

Ответы [ 2 ]

0 голосов
/ 08 ноября 2018

Лучшее решение, которое я нашел, это слегка модифицированная версия: JavaFX с возможностью поиска комбинированного списка (например, 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 добавляется в качестве changeListener для текстового поля Combobox:

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

В настоящее время Combobox.setEditable(true) нужно сделать вручную снаружи, но я планирую переместить это в сам InputFilter. Также вам нужно установить String конвертер для Combobox. Пока это решение мне не по душе, единственное, чего не хватает, - это поддержки клавиши пробела при вводе ключа поиска.

0 голосов
/ 05 ноября 2018

Я бы предложил создать 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);

    }
}

У вас будет простой редактируемый ComboBox, отфильтровывающий значения из списка, которые не совпадают.


При использовании этого метода вам не нужно прослушивать каждое нажатие клавиши, но вы можете предоставить любые инструкции по фильтрации внутри самого Predicate, как показано выше.


РЕЗУЛЬТАТЫ:

screenshot

screenshot

EDIT:

Существуют некоторые проблемы с редактируемым 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

...