Нет изменений в ListView при вводе текста в фильтр TextField

EDIT --- Показаны классы объектов, добавлен код внутри метода инициализации, удален прежний метод фильтра и удалено OnAction for TextField из FXML. ---

Мои первые попытки выяснить это привели ко многим проблемам с Android, так что я здесь.

Я сделал простой графический интерфейс, используя JavaFX, Scenebuilder и FXML. Этот графический интерфейс имитирует инвентарь цеха завода и может добавлять / удалять из / отображать в / сохранять в файл из ObservableList, Также предполагается фильтровать объекты на основе частичного Stringвведены пользователем в TextField,

Это было просто в предыдущем воплощении этой программы, так как все это было через командную строку; однако, я столкнулся с новой проблемой, которую я никогда не видел прежде.

Каждый объект печатается с псевдослучайным идентификационным номером с указанным пользователем префиксом (F для цветка, W для травы, H для травы, Fn для грибка), именами, цветом и логическими атрибутами, такими как "колючий", "ароматизированный", "съедобные" и т. д. Они определяются с помощью кнопок радио. Например, если пользователь вводит все значения нового цветка, он печатает как: "ID: F-21, имя: роза, цвет: красный, колючий? Ложный, ароматизированный? Истинный"

Вот мой начальный ObservableList и FilteredList:

ObservableList<Plant> observablePlantList = FXCollections.observableArrayList();
FilteredList<Plant> filteredList = new FilteredList<Plant>(observablePlantList);

Вот мой класс Plant и один подкласс (их 4 одинаковых), чтобы показать, как они отображаются в списке (логические значения - это Radio Buttons):

    public class Plant {
public String ID;
public String name;
public String color;
public boolean smell;
public boolean thorns;
public boolean edible;
public boolean poisonous;
public boolean flavor;
public boolean medicine;
public boolean seasonal;
public int idNum;

public Plant(String ID, int idNum, String name, String color, boolean smell, boolean thorns, boolean edible, boolean poisonous, boolean flavor, boolean medicine, boolean seasonal) {
    this.ID = ID;
    this.idNum = randomID(idNum);
    this.name = name;
    this.color = color;
    this.smell = false;
    this.thorns = false;
    this.edible = false;
    this.poisonous = false;
    this.flavor = false;
    this.medicine = false;
    this.seasonal = false;
}

public void setColor(String color) {this.color = color;}
public void setID(String ID) {this.ID = ID;}
public void setName(String name) {this.name = name;}

public String getID() {return ID;}
public String getName() {return name;}

public int randomID(int idNum) {
    Random randomNum = new Random();
    idNum = randomNum.nextInt(50);
    return idNum;
}

public String toString() {

    return "ID: " + this.ID + "-" + this.idNum + ", Name: " + this.name + ", Color: " + this.color;
}

}

Подкласс

    public class Flower extends Plant {

public Flower(String ID, int idNum, String name, String color, boolean smell, boolean thorns, boolean edible, boolean poisonous, boolean flavor, boolean medicine, boolean seasonal) {

    super(ID, idNum, name, color, smell, thorns, edible, poisonous, flavor, medicine, seasonal);
}
public void setSmell(boolean smell) {
    this.smell = smell;
}
public void setThorns(boolean thorns) {
    this.thorns = thorns;
}
public String toString() {

    return super.toString() + ", Scent? " + this.smell + ", Thorns? " + this.thorns;
}

}

Это то, что у меня есть внутри моего метода инициализации, благодаря предложению в ответе ниже. Проблема в том, что ничего не меняется:

    @Override
public void initialize(URL fxmlFileLocation, ResourceBundle rb) {

    plantList.setItems(filteredList);

    //binding filterInput text entries
    filteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> {
        String filterText = filterInput.getText();
        if(filterText == null || filterText.isEmpty()) {
            return null;
        }
        else {
            final String uppercase = filterText.toUpperCase();
            return (plant) -> plant.getName().toUpperCase().contains(uppercase);
        }
    }, filterInput.textProperty()));

Моя цель состоит в том, чтобы отфильтровать ObservableArrayList в режиме реального времени, а не использовать кнопку, которая фильтрует и показывает элементы в новой сцене. Я видел, как это реализовано, и действительно нравится функциональность.

Спасибо за любую предложенную помощь.

2 ответа

Решение

Мне нужно изучить новые способы, представленные в новых версиях JavaFX. Я подумал о том, что я на самом деле хотел сделать, выписал это на бумаге и написал в меру своих способностей / текущих знаний.

Вот рабочий код, все еще с ChangeListener в методе initialize, и OnAction все еще удален из файла FXML. Теперь, когда я набираю символ, появляется соответствующее растение и показывает оригинал при нажатии на клавишу возврата.

Я искренне извиняюсь за то, что не использовал то, что вы предложили. Я еще недостаточно опытен, чтобы полностью понимать предикаты и привязки, но у меня определенно есть что исследовать!

     public void filterPlantList(String oldValue, String newValue) {

    ObservableList<Plant> filteredList = FXCollections.observableArrayList();
    if(filterInput == null || (newValue.length() < oldValue.length()) || newValue == null) {
        plantList.setItems(observablePlantList);
    }
    else {
        newValue = newValue.toUpperCase();
        for(Plant plants : plantList.getItems()) {
            String filterText = plants.getName();
            if(filterText.toUpperCase().contains(newValue)) {
                filteredList.add(plants);
            }
        }
        plantList.setItems(filteredList);
    }
}
@Override
public void initialize(URL fxmlFileLocation, ResourceBundle rb) {

    //add Listener to filterInput TextField
    filterInput.textProperty().addListener(new ChangeListener() {
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
            filterPlantList((String) oldValue, (String) newValue);
        }
    });

Что не так с вашим кодом:

  • вы регистрируете слушателя в onAction обработчик события TextField вместо Initializable.initialize метод контроллера. Это означает, что слушатель будет зарегистрирован, когда Action событие происходит (по умолчанию, когда вы нажимаете ENTER) и новый будет зарегистрирован каждый раз, когда Action событие происходит.
  • Вы назначаете filterItems к items собственность вашего ListView, но если текст становится короче, вы не назначаете новое значение перед повторной фильтрацией. Это означает plantList.getItems() (список, по которому вы перебираете) может быть тем же списком, к которому вы добавляете элементы => ConcurrentModificationException,
  • вы никогда не очищаете отфильтрованный список

Более элегантный способ

использование FilteredList, Это класс, предназначенный для фильтрации.

@FXML
private ListView<Plant> plantList;
@FXML
private TextField filterInput;

ObservableList<Plant> observablePlantList = FXCollections.observableArrayList();
FilteredList<Plant> filterItems = new FilteredList<>(observablePlantList);

@Override
public void initialize(URL url, ResourceBundle rb) {
    plantList.setItems(filterItems);

    // bind predicate to text filterInput text
    filterItems.predicateProperty().bind(javafx.beans.binding.Bindings.createObjectBinding(() -> {
        String text = filterInput.getText();
        if (text == null || text.isEmpty()) {
            return null;
        } else {
            final String uppercase = text.toUpperCase();
            return (plant) -> plant.getName().toUpperCase().contains(uppercase);
        }
    }, filterInput.textProperty()));
}

Конечно, вы удалите onAction атрибут из TextField в файле fxml в этом случае.

Другие вопросы по тегам