编辑: 我试图用搜索功能构建一个组合框,这就是我想出的:
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);
}
}
这个想法很简单:只要打开了“组合框”的“下拉列表”,我就会监听按键并将字符添加到过滤器中。使用这些过滤器,组合框的项目列表被过滤为仅包含包含过滤字符串的项目的列表。然后,我使用setItems将项目列表设置为我的过滤列表。我的问题是,组合框的valueProperty更改了,但是我希望所选对象保持不变,直到用户从下拉列表中选择另一个。我在ValueProperty中添加了一个ChangeListener:
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()));
}
}
});
当值更改时,控制台如下所示:
值已更改
Andersen,Wiebke(对象的字符串表示形式)
或类似这样:
值已更改
null(对象为null)
基本上有3种情况正在发生。首先是我打开下拉列表,不选择一个项目并输入我的过滤器。然后我选择一个项目,然后我的照片将向我显示:
值已更改
安德森(Andebsen),威布(Wiebke)
值已更改
空
值已更改
安德森(Andebsen),威布(Wiebke)
第二种情况是我打开下拉列表并选择一个项目。我现在继续输入过滤器,并且所选项目包含该过滤器。我的照片将向我展示:
值已更改
空
值已更改
安德森(Andebsen),威布(Wiebke)
每次选择安德森(Andersen)时,每次按下一个键,都再次Wiebke /关闭下拉列表。
第三种情况是选择一个项目,然后继续键入所选项目不包含的过滤器。一旦选定的项目不再包含过滤器,则valueProperty的值将更改为null。如果我选择一个新项目,那么我得到这个:
值已更改
Karzi Budziszewski
值已更改
空
值已更改
Karzi Budziszewski
我想要的是ValueProperty不会更改,直到用户从下拉列表中选择一个新项目。我也很想知道为什么价值属性对我一直都会变化。特别是因为我并不真正认为我的解决方案与Zephyr提供的解决方案之间存在根本差异。我们都使用过滤字符串过滤原始列表,然后使用setItems()将组合框的列表设置为新过滤的列表。正如下面的评论中所述,我什至不能使用他的解决方案,因为我无法使Combobox的setEditable正常工作:
当我尝试personalauswahl.setEditable(true);我由以下原因引起:de.statistik_nord.klr.controller.EingabeController $ 1.toString(EingabeController.java:93)的java.lang.NullPointerException .de.statistik_nord.klr.controller.EingabeController $ 1.toString(EingabeController.java:1)指向以下代码行:return object.getName()+“,” + object.getVorname();
答案 0 :(得分:2)
我建议您从原始列表中创建一个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);
}
}
您将拥有一个简单的可编辑组合框,该组合框从列表中过滤出不匹配的值。
使用这种方法,您无需监听每次按键,但可以在Predicate
本身内提供任何过滤指令,如上所示。
结果:
编辑:
可编辑的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);
}
}
答案 1 :(得分:0)
我发现的最佳解决方案是稍作修改的版本:JavaFX searchable combobox (like js select2)
我修改过的东西是使InputFilter类通用,并且组合框在关闭下拉列表后失去了焦点。这里的代码:
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添加为组合框的textField的changeListener:
comboBox.getEditor().textProperty().addListener(new InputFilter<YourCustomClass>(comboBox, new FilteredList<YourCustomClass>(comboBox.getItems())));
当前Combobox.setEditable(true)
必须在外部手动完成,但是我计划将其移至InputFilter本身。另外,您需要为组合框设置一个字符串转换器。到目前为止,该解决方案对我来说是不小的麻烦,唯一缺少的是在键入搜索键时支持空格键。