Jon*_*cka 5 java combobox javafx predicate filter
我已经实现了一个ComboBox列表被其中的输入过滤的位置ComboBox TextField.它可以正常工作,因为你可能期望这种控件的过滤器工作.列表中显示列表中以输入文本开头的每个项目.
我只有一个小问题.如果我从列表中选择一个项目,然后尝试删除文本字段中的最后一个字符,则没有任何反应.如果我从列表中选择一个项目,然后尝试删除除最后一个之外的任何其他字符,则会删除整个字符串.只有这是我做的第一件事,才会出现这两个问题ComboBox.如果我先在组合框中写一些东西,或者我第二次选择一个项目,则不会出现所描述的任何问题.
对我来说真正奇怪的是,这些问题似乎是由谓词设置引起的(如果我注释掉调用setPredicate,一切正常).这很奇怪,因为我认为这应该只影响为谓词设置的列表.它不应该影响剩下的ComboBox.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class TestInputFilter extends Application {
public void start(Stage stage) {
VBox root = new VBox();
ComboBox<ComboBoxItem> cb = new ComboBox<ComboBoxItem>();
cb.setEditable(true);
cb.setConverter(new StringConverter<ComboBoxItem>() {
@Override
// To convert the ComboBoxItem to a String we just call its
// toString() method.
public String toString(ComboBoxItem object) {
return object == null ? null : object.toString();
}
@Override
// To convert the String to a ComboBoxItem we loop through all of
// the items in the combobox dropdown and select anyone that starts
// with the String. If we don't find a match we create our own
// ComboBoxItem.
public ComboBoxItem fromString(String string) {
return cb.getItems().stream().filter(item -> item.getText().startsWith(string)).findFirst()
.orElse(new ComboBoxItem(string));
}
});
ObservableList<ComboBoxItem> options = FXCollections.observableArrayList(new ComboBoxItem("One is a number"),
new ComboBoxItem("Two is a number"), new ComboBoxItem("Three is a number"),
new ComboBoxItem("Four is a number"), new ComboBoxItem("Five is a number"),
new ComboBoxItem("Six is a number"), new ComboBoxItem("Seven is a number"));
FilteredList<ComboBoxItem> filteredOptions = new FilteredList<ComboBoxItem>(options, p -> true);
cb.setItems(filteredOptions);
InputFilter inputFilter = new InputFilter(cb, filteredOptions);
cb.getEditor().textProperty().addListener(inputFilter);
root.getChildren().add(cb);
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch();
}
class ComboBoxItem {
private String text;
public ComboBoxItem(String text) {
this.text = text;
}
public String getText() {
return text;
}
@Override
public String toString() {
return text;
}
}
class InputFilter implements ChangeListener<String> {
private ComboBox<ComboBoxItem> box;
private FilteredList<ComboBoxItem> items;
public InputFilter(ComboBox<ComboBoxItem> box, FilteredList<ComboBoxItem> items) {
this.box = box;
this.items = items;
}
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String value = newValue;
// If any item is selected we get the first word of that item.
String selected = box.getSelectionModel().getSelectedItem() != null
? box.getSelectionModel().getSelectedItem().getText() : null;
// If an item is selected and the value of in the editor is the same
// as the selected item we don't filter the list.
if (selected != null && value.equals(selected)) {
items.setPredicate(item -> {
return true;
});
} else {
items.setPredicate(item -> {
if (item.getText().toUpperCase().startsWith(value.toUpperCase())) {
return true;
} else {
return false;
}
});
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:我试图在绝望的尝试中覆盖关键监听器来解决问题:
cb.getEditor().addEventFilter(KeyEvent.KEY_PRESSED, e -> {
TextField editor = cb.getEditor();
int caretPos = cb.getEditor().getCaretPosition();
StringBuilder text = new StringBuilder(cb.getEditor().getText());
// If BACKSPACE is pressed we remove the character at the index
// before the caret position.
if (e.getCode().equals(KeyCode.BACK_SPACE)) {
// BACKSPACE should only remove a character if the caret
// position isn't zero.
if (caretPos > 0) {
text.deleteCharAt(--caretPos);
}
e.consume();
}
// If DELETE is pressed we remove the character at the caret
// position.
else if (e.getCode().equals(KeyCode.DELETE)) {
// DELETE should only remove a character if the caret isn't
// positioned after that last character in the text.
if (caretPos < text.length()) {
text.deleteCharAt(caretPos);
}
}
// If LEFT key is pressed we move the caret one step to the left.
else if (e.getCode().equals(KeyCode.LEFT)) {
caretPos--;
}
// If RIGHT key is pressed we move the caret one step to the right.
else if (e.getCode().equals(KeyCode.RIGHT)) {
caretPos++;
}
// Otherwise we just add the key text to the text.
// TODO We are currently not handling UP/DOWN keys (should move
// caret to the end/beginning of the text).
// TODO We are currently not handling keys that doesn't represent
// any symbol, like ALT. Since they don't have a text, they will
// just move the caret one step to the right. In this case, that
// caret should just hold its current position.
else {
text.insert(caretPos++, e.getText());
e.consume();
}
final int finalPos = caretPos;
// We set the editor text to the new text and finally we move the
// caret to its new position.
editor.setText(text.toString());
Platform.runLater(() -> editor.positionCaret(finalPos));
});
// We just consume KEY_RELEASED and KEY_TYPED since we don't want to
// have duplicated input.
cb.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, e -> {
e.consume();
});
cb.getEditor().addEventFilter(KeyEvent.KEY_TYPED, e -> {
e.consume();
});
Run Code Online (Sandbox Code Playgroud)
可悲的是,这也没有解决问题.如果我例如选择"Three is a number"项,然后尝试删除"Three"中的最后一个"e",则这是text属性将在之间切换的值:
TextProperty: Three is a number
TextPropery: Thre is a number
TextPropery:
Run Code Online (Sandbox Code Playgroud)
所以它首先删除了正确的字符,然后String由于某种原因删除了整个字符 .如前所述,这只是因为谓词已经设置,而且只有在我第一次选择项目后进行第一次输入时才会发生这种情况.
乔纳坦,
正如 Manuel 所说,一个问题是 setPredicate() 会触发changed() 方法两次,因为您正在更改组合框模型,但真正的问题是组合框将用任何看起来合适的值覆盖编辑器值。以下是对您的症状的解释:
如果我从列表中选择一个项目,然后尝试删除文本字段中的最后一个字符,则不会发生任何情况。
在这种情况下,最后一个字符的删除实际上正在发生,但是对 setPredicate() 的第一次调用匹配一个可能的项目(与您删除最后一个字符的项目完全相同)并将组合框内容更改为仅一个项目。这会导致组合框使用当前的combobox.getValue()字符串恢复编辑器值的调用,给人一种什么都没有发生的错觉。它还会导致对changed()方法的第二次调用,但此时编辑器文本已经更改。
为什么这种情况只发生第一次,然后就再也没有发生过?
好问题!这种情况只会发生一次,因为您正在修改组合框的整个底层模型一次(如前所述,这会触发对changed()方法的第二次调用)。
因此,在发生前一种情况后,如果您单击下拉按钮(右箭头),您将看到只剩下一个项目,如果您尝试再次删除一个角色,您仍然会留下相同的项目,即模型(组合框的内容)没有改变,因为 setPredicate() 仍然会匹配相同的内容,因此不会导致 TextInputControl 类中的 markInvalid() 调用,因为内容实际上没有改变,这意味着不会再次恢复项目字符串(如果您想查看文本字段第一次实际恢复的位置,请参阅带有 JavaFX 源的 ComboBoxPopupControl.updateDisplayNode() 方法)。
如果我从列表中选择一个项目,然后尝试删除最后一个字符以外的任何其他字符,则整个字符串将被删除。
在您的第二个场景中,没有任何内容与第一个 setPredicate() 调用匹配(没有项目与您的startsWith条件匹配),这会删除组合框中的所有项目,也会删除您当前的选择和编辑器字符串。
提示:尝试自己理解这一点,在changed()方法内切换一个断点,看看它输入了多少次以及原因(如果您想跟踪ComboBox及其组件行为,则需要JavaFX源代码)
解决方案: 如果您想继续使用 ChangeListener,您可以通过在过滤后恢复编辑器中的文本来解决您的主要问题(即 setPredicate 调用后编辑器内容被替换):
class InputFilter implements ChangeListener<String> {
private ComboBox<ComboBoxItem> box;
private FilteredList<ComboBoxItem> items;
public InputFilter(ComboBox<ComboBoxItem> box, FilteredList<ComboBoxItem> items) {
this.box = box;
this.items = items;
}
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String value = newValue;
// If any item is selected we get the first word of that item.
String selected = box.getSelectionModel().getSelectedItem() != null
? box.getSelectionModel().getSelectedItem().getText() : null;
// If an item is selected and the value of in the editor is the same
// as the selected item we don't filter the list.
if (selected != null && value.equals(selected)) {
items.setPredicate(item -> {
return true;
});
} else {
// This will most likely change the box editor contents
items.setPredicate(item -> {
if (item.getText().toUpperCase().startsWith(value.toUpperCase())) {
return true;
} else {
return false;
}
});
// Restore the original search text since it was changed
box.getEditor().setText(value);
}
//box.show(); // <-- Uncomment this line for a neat look
}
}
Run Code Online (Sandbox Code Playgroud)
就我个人而言,我过去曾使用 KeyEvent 处理程序完成此操作(以避免在changed() 事件中多次调用我的代码),但是您始终可以使用 Semaphore 或 java.util.concurrent 类中您最喜欢的类来避免如果您觉得自己开始需要它,则任何不必要的重新进入您的方法。现在,即使相同的方法冒泡两次或三次, getEditor().setText() 也始终会尾部恢复正确的值。
希望这可以帮助!
| 归档时间: |
|
| 查看次数: |
1621 次 |
| 最近记录: |