我的应用程序包含一个TextField
和一个ListView
。 TextField
允许用户输入搜索词,它们将在输入时过滤ListView
的内容。
过滤过程将匹配DataItem
中每个ListView
中的多个字段,并返回匹配的结果。
但是,我想要做的是使那些结果优先匹配某个特定字段的项目。
例如,在下面的MCVE中,我有两个项目:Computer
和Paper
。 Computer
项目的“纸张”为keyword
,因此搜索“ paper”应返回Computer
。
但是,由于我还有一个名为Paper
的项目,因此搜索应在列表顶部返回Paper
。但是,在MCVE中,结果仍然按字母顺序排列:
问题:如何确保与DataItem.name
的匹配项已在与DataItem.keywords
的匹配项中列出?
编辑:在搜索字段中输入“ pap”也应在顶部返回“ Paper”,然后返回其余匹配项,因为部分搜索项与DataItem
的名称部分匹配
MCVE
import java.util.List;
public class DataItem {
// Instance properties
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty name = new SimpleStringProperty();
private final StringProperty description = new SimpleStringProperty();
// List of search keywords
private final ObjectProperty<List<String>> keywords = new SimpleObjectProperty<>();
public DataItem(int id, String name, String description, List<String> keywords) {
this.id.set(id);
this.name.set(name);
this.description.set(description);
this.keywords.set(keywords);
}
/**
* Creates a space-separated String of all the keywords; used for filtering later
*/
public String getKeywordsString() {
StringBuilder sb = new StringBuilder();
for (String keyword : keywords.get()) {
sb.append(keyword).append(" ");
}
return sb.toString();
}
public int getId() {
return id.get();
}
public IntegerProperty idProperty() {
return id;
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public String getDescription() {
return description.get();
}
public StringProperty descriptionProperty() {
return description;
}
public List<String> getKeywords() {
return keywords.get();
}
public ObjectProperty<List<String>> keywordsProperty() {
return keywords;
}
@Override
public String toString() {
return name.get();
}
}
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class Main extends Application {
// TextField used for filtering the ListView
TextField txtSearch = new TextField();
// ListView to hold our DataItems
ListView<DataItem> dataItemListView = new ListView<>();
// The ObservableList of DataItems
ObservableList<DataItem> dataItems;
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 the search field and ListView to the layout
root.getChildren().addAll(txtSearch, dataItemListView);
// Build the dataItems List
dataItems = FXCollections.observableArrayList(buildDataItems());
// Add the filter logic
addSearchFilter();
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
/**
* Adds the functionality to filter the list dynamically as search terms are entered
*/
private void addSearchFilter() {
// Wrap the dataItems list in a filtered list, initially showing all items, alphabetically
FilteredList<DataItem> filteredList = new FilteredList<>(
dataItems.sorted(Comparator.comparing(DataItem::getName)));
// Add the predicate to filter the list whenever the search field changes
txtSearch.textProperty().addListener((observable, oldValue, newValue) ->
filteredList.setPredicate(dataItem -> {
// Clear any selection already present
dataItemListView.getSelectionModel().clearSelection();
// If the search field is empty, show all DataItems
if (newValue == null || newValue.isEmpty()) {
return true;
}
// Compare the DataItem's name and keywords with the search query (ignoring case)
String query = newValue.toLowerCase();
if (dataItem.getName().toLowerCase().contains(query)) {
// DataItem's name contains the search query
return true;
} else {
// Otherwise check if any of the search terms match those in the DataItem's keywords
// We split the query by space so we can match DataItems with multiple keywords
String[] searchTerms = query.split(" ");
boolean match = false;
for (String searchTerm : searchTerms) {
match = dataItem.getKeywordsString().toLowerCase().contains(searchTerm);
}
return match;
}
}));
// Wrap the filtered list in a SortedList
SortedList<DataItem> sortedList = new SortedList<>(filteredList);
// Update the ListView
dataItemListView.setItems(sortedList);
}
/**
* Generates a list of sample products
*/
private List<DataItem> buildDataItems() {
List<DataItem> dataItems = new ArrayList<>();
dataItems.add(new DataItem(
1, "School Supplies", "Learn things.",
Arrays.asList("pens", "pencils", "paper", "eraser")));
dataItems.add(new DataItem(
2, "Computer", "Do some things",
Arrays.asList("paper", "cpu", "keyboard", "monitor")));
dataItems.add(new DataItem(
3, "Keyboard", "Type things",
Arrays.asList("keys", "numpad", "input")));
dataItems.add(new DataItem(
4, "Printer", "Print things.",
Arrays.asList("paper", "ink", "computer")));
dataItems.add(new DataItem(
5, "Paper", "Report things.",
Arrays.asList("write", "printer", "notebook")));
return dataItems;
}
}
答案 0 :(得分:4)
如果没记错的话,您只需要找到一种方法就可以正确地对过滤的结果进行排序。为简单起见,我将使用此比较器代替您的比较器:
Comparator<DataItem> byName = new Comparator<DataItem>() {
@Override
public int compare(DataItem o1, DataItem o2) {
String searchKey = txtSearch.getText().toLowerCase();
int item1Score = findScore(o1.getName().toLowerCase(), searchKey);
int item2Score = findScore(o2.getName().toLowerCase(), searchKey);
if (item1Score > item2Score) {
return -1;
}
if (item2Score > item1Score) {
return 1;
}
return 0;
}
private int findScore(String itemName, String searchKey) {
int sum = 0;
if (itemName.startsWith(searchKey)) {
sum += 2;
}
if (itemName.contains(searchKey)) {
sum += 1;
}
return sum;
}
};
在上面的代码中,我比较了两个DataItem。每个人都有一个“分数”,这取决于我们的搜索关键字中他们的名字有多相似。为简单起见,假设如果searchKey
出现在我们的商品名称中,则给出1分;如果商品名称以searchKey
开头,则给出2分,因此,我们现在可以比较这两点并对其进行排序。如果我们返回-1,则item1将首先放置,如果我们返回1,则item2将首先放置,否则返回0。
这是我在您的示例中使用的addSearchFilter()
方法:
private void addSearchFilter() {
FilteredList<DataItem> filteredList = new FilteredList<>(dataItems);
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> filteredList.setPredicate(dataItem -> {
dataItemListView.getSelectionModel().clearSelection();
if (newValue == null || newValue.isEmpty()) {
return true;
}
String query = newValue.toLowerCase();
if (dataItem.getName().toLowerCase().contains(query)) {
return true;
} else {
String[] searchTerms = query.split(" ");
boolean match = false;
for (String searchTerm : searchTerms) {
match = dataItem.getKeywordsString().toLowerCase().contains(searchTerm);
}
return match;
}
}));
SortedList<DataItem> sortedList = new SortedList<>(filteredList);
Comparator<DataItem> byName = new Comparator<DataItem>() {
@Override
public int compare(DataItem o1, DataItem o2) {
String searchKey = txtSearch.getText().toLowerCase();
int item1Score = findScore(o1.getName().toLowerCase(), searchKey);
int item2Score = findScore(o2.getName().toLowerCase(), searchKey);
if (item1Score > item2Score) {
return -1;
}
if (item2Score > item1Score) {
return 1;
}
return 0;
}
private int findScore(String itemName, String searchKey) {
int sum = 0;
if (itemName.startsWith(searchKey)) {
sum += 2;
}
if (itemName.contains(searchKey)) {
sum += 1;
}
return sum;
}
};
sortedList.setComparator(byName);
dataItemListView.setItems(sortedList);
}
如果您要创建更复杂的评分系统(例如,检查大写和小写字母,根据项目名称中找到的关键字的位置给出更多的分数等),当然findScore()
可能会更复杂。 )。
答案 1 :(得分:2)
我可能找到了另一种方法来完成此任务。我没有使用Predicate
,而是将ChangeListener
更改为仅使用几个循环并手动构建了一个新的List
:
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == null || newValue.isEmpty()) {
// Reset the ListView to show all items
dataItemListView.setItems(dataItems);
return;
}
ObservableList<DataItem> filteredList = FXCollections.observableArrayList();
String query = newValue.toLowerCase().trim();
// First, look for exact matches within the DataItem's name
for (DataItem item : dataItems) {
if (item.getName().toLowerCase().contains(query)) {
filteredList.add(0, item);
} else {
// If the item's name doesn't match, we'll look through search terms instead
String[] searchTerms = query.split(" ");
for (String searchTerm : searchTerms) {
// If the item has this searchTerm and has not already been added to the filteredList, add it
// now
if (item.getKeywordsString().toLowerCase().contains(searchTerm)
&& !filteredList.contains(item)) {
filteredList.add(item);
}
}
}
}
dataItemListView.setItems(filteredList);
我现在暂时不回答这个问题,看看是否有人也可以使用Predicate
。