在过滤器 TextField 中输入文本时,ListView 没有变化
No change to ListView when text is entered in filter TextField
EDIT --- 显示对象 classes,在初始化方法中添加代码,删除以前的过滤器方法,以及从 FXML 中删除了 TextField 的 OnAction。 ---
我第一次尝试解决这个问题时遇到了很多 Android 问题,所以我来了。
我使用 JavaFX、Scenebuilder 和 FXML 制作了一个简单的 GUI。此 GUI 模拟植物商店的库存,并且可以将 to/remove from/display in/save 添加到来自 ObservableList
的文件中。它还应该根据用户在 TextField
中输入的部分 String
来过滤对象。
这在这个程序的前身中很简单,因为它都是通过命令行实现的;但是,我 运行 遇到了一个我以前从未见过的新问题。
每个对象打印一个带有用户指定前缀的伪随机 ID 号(F 代表花,W 代表杂草,H 代表香草,Fn 代表真菌)、名称、颜色和布尔属性,例如 "thorny"、"scented"、"edible" 等。这些由单选按钮确定。例如,如果用户输入一朵新花的所有值,它会打印为:"ID: F-21, Name: Rose, Color: Red, Thorny? False, Scented? True"
这是我最初的 ObservableList 和 FilteredList:
ObservableList<Plant> observablePlantList = FXCollections.observableArrayList();
FilteredList<Plant> filteredList = new FilteredList<Plant>(observablePlantList);
这是我的植物 class 和一个子 class(有 4 个完全相同)以显示它们在列表中的显示方式(布尔值是单选按钮):
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;
}
}
亚class
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,而不是使用按钮过滤和显示新场景中的项目。我已经看到了这个实现,并且非常喜欢这个功能。
感谢您提供的任何帮助。
你的代码有什么问题:
- 您在
TextField
的 onAction
事件处理程序而不是控制器的 Initializable.initialize
方法中注册侦听器。这意味着监听器将在 Action
事件发生时注册(默认情况下,当您点击 ENTER
时),并且每次 将注册一个新监听器 Action
事件发生。
- 您将
filterItems
分配给 ListView
的 items
属性,但如果文本变短,您不会在再次过滤之前分配新值.这意味着 plantList.getItems()
(您迭代的列表)可以与您添加项目的列表相同 => ConcurrentModificationException
.
- 您永远不会清除过滤后的列表
更优雅的方式
使用FilteredList
。这是一个 class 旨在为您进行过滤。
@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()));
}
当然,在这种情况下,您从 fxml 文件中的 TextField
中删除了 onAction
属性。
我需要学习引入新版本 JavaFX 的新方法。我考虑了我真正想做的事情,把它写在纸上,并尽我 ability/current 的知识写下来。
这是工作代码,在初始化方法中仍然有一个 ChangeListener,并且仍然从 FXML 文件中删除了 OnAction。现在,当我输入一个字符时,相应的植物就会出现,并在按下退格键时显示原始植物。
对于没有使用您建议的内容,我深表歉意。我还没有足够的经验来完全理解谓词和绑定,但我肯定有一些东西要研究!
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);
}
});
EDIT --- 显示对象 classes,在初始化方法中添加代码,删除以前的过滤器方法,以及从 FXML 中删除了 TextField 的 OnAction。 ---
我第一次尝试解决这个问题时遇到了很多 Android 问题,所以我来了。
我使用 JavaFX、Scenebuilder 和 FXML 制作了一个简单的 GUI。此 GUI 模拟植物商店的库存,并且可以将 to/remove from/display in/save 添加到来自 ObservableList
的文件中。它还应该根据用户在 TextField
中输入的部分 String
来过滤对象。
这在这个程序的前身中很简单,因为它都是通过命令行实现的;但是,我 运行 遇到了一个我以前从未见过的新问题。
每个对象打印一个带有用户指定前缀的伪随机 ID 号(F 代表花,W 代表杂草,H 代表香草,Fn 代表真菌)、名称、颜色和布尔属性,例如 "thorny"、"scented"、"edible" 等。这些由单选按钮确定。例如,如果用户输入一朵新花的所有值,它会打印为:"ID: F-21, Name: Rose, Color: Red, Thorny? False, Scented? True"
这是我最初的 ObservableList 和 FilteredList:
ObservableList<Plant> observablePlantList = FXCollections.observableArrayList();
FilteredList<Plant> filteredList = new FilteredList<Plant>(observablePlantList);
这是我的植物 class 和一个子 class(有 4 个完全相同)以显示它们在列表中的显示方式(布尔值是单选按钮):
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;
}
}
亚class
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,而不是使用按钮过滤和显示新场景中的项目。我已经看到了这个实现,并且非常喜欢这个功能。
感谢您提供的任何帮助。
你的代码有什么问题:
- 您在
TextField
的onAction
事件处理程序而不是控制器的Initializable.initialize
方法中注册侦听器。这意味着监听器将在Action
事件发生时注册(默认情况下,当您点击ENTER
时),并且每次 将注册一个新监听器Action
事件发生。 - 您将
filterItems
分配给ListView
的items
属性,但如果文本变短,您不会在再次过滤之前分配新值.这意味着plantList.getItems()
(您迭代的列表)可以与您添加项目的列表相同 =>ConcurrentModificationException
. - 您永远不会清除过滤后的列表
更优雅的方式
使用FilteredList
。这是一个 class 旨在为您进行过滤。
@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()));
}
当然,在这种情况下,您从 fxml 文件中的 TextField
中删除了 onAction
属性。
我需要学习引入新版本 JavaFX 的新方法。我考虑了我真正想做的事情,把它写在纸上,并尽我 ability/current 的知识写下来。
这是工作代码,在初始化方法中仍然有一个 ChangeListener,并且仍然从 FXML 文件中删除了 OnAction。现在,当我输入一个字符时,相应的植物就会出现,并在按下退格键时显示原始植物。
对于没有使用您建议的内容,我深表歉意。我还没有足够的经验来完全理解谓词和绑定,但我肯定有一些东西要研究!
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);
}
});