Search code examples
javafxtextfieldfxmlpredicateobservablelist

No change to ListView when text is entered in filter TextField


EDIT --- Showed Object classes, added code inside initialize method, removed former filter method, and removed OnAction for TextField from FXML. ---

My first attempts at trying to figure this out resulted in many Android problems, so here I am.

I made a simple GUI using JavaFX, Scenebuilder, and FXML. This GUI simulates the inventory of a plant shop, and can add to/remove from/display in/save to a file from an ObservableList. It is also supposed to filter objects based on partial Strings entered by the user in a TextField.

This was simple in a former incarnation of this program, since it was all via command line; however, I've run into a new problem that I've never seen before.

Each object prints with a pseudo-random ID number with user-specified prefix (F for Flower, W for Weed, H for Herb, Fn for Fungus), the Name, Color, and boolean attributes such as "thorny", "scented", "edible", etc. Those are determined by Radio Buttons. For example, if the user enters all the values of a new Flower, it prints as: "ID: F-21, Name: Rose, Color: Red, Thorny? False, Scented? True"

Here is my initial ObservableList and FilteredList:

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

Here is my Plant class and one subclass (there are 4 exactly the same) to show how they display in the list (booleans are 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;
}

}

Subclass

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

}

This is what I have inside my initialize method thanks to the suggestion in the answer below. The problem is that nothing changes:

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

My goal with this is to filter an ObservableArrayList in real-time, rather than use a button that filters and shows items in a new Scene. I have seen this implemented, and really like the functionality.

Thank you for any help offered.


Solution

  • I need to learn new ways introduced to newer versions of JavaFX. I thought about what I actually wanted to do, wrote it out on paper, and wrote it to the best of my ability/current knowledge.

    Here is the working code, still with a ChangeListener in the initialize method, and OnAction still removed from FXML file. Now, when I type a character, the respective plant shows up, and shows the original upon hitting backspace.

    I sincerely apoligize for not utilizing what you suggested. I'm not experienced enough yet to fully understand predicates and bindings, but I definitely have something to research!

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