我的一位开发人员尝试根据用户输入的内容将ComboBox扩展为自动过滤器:
public class AutoCompleteComboBox<T> extends ComboBox<T> {
private FilteredList<T> filteredItems;
private SortedList<T> sortedItems;
public AutoCompleteComboBox() {
setEditable(true);
setOnKeyReleased(e -> handleOnKeyReleasedEvent(e));
setOnMouseClicked(e -> handleOnMouseClicked(e));
}
private void handleOnMouseClicked(MouseEvent e) {
getItems().stream()
.filter(item -> item.toString().equals(getEditor().getText()))
.forEach(item -> getSelectionModel().select(item));
setCaretPositionToEnd();
}
private void handleOnKeyReleasedEvent(KeyEvent e) {
if (e.getCode() == KeyCode.UP || e.getCode() == KeyCode.DOWN) {
getItems().stream()
.filter(item -> item.toString().equals(getEditor().getText()))
.forEach(item -> getSelectionModel().select(item));
show();
setCaretPositionToEnd();
} else if (e.getCode() == KeyCode.ENTER || e.getCode() == KeyCode.TAB) {
String editorStr = getEditor().getText();
getSelectionModel().clearSelection();
getEditor().setText(editorStr);
setItems(this.sortedItems);
getItems().stream()
.filter(item -> item.toString().equals(editorStr))
.forEach(item -> getSelectionModel().select(item));
getEditor().selectEnd();
if (e.getCode() == KeyCode.ENTER) {
getEditor().deselect();
}
hide();
} else if (e.getText().length() == 1) {
getSelectionModel().clearSelection();
if (getEditor().getText().length() == 0) {
getEditor().setText(e.getText());
}
filterSelectionList();
show();
} else if (e.getCode() == KeyCode.BACK_SPACE && getEditor().getText().length() > 0) {
String editorStr = getEditor().getText();
getSelectionModel().clearSelection();
getEditor().setText(editorStr);
int beforeFilter = getItems().size();
filterSelectionList();
int afterFilter = getItems().size();
if (afterFilter > beforeFilter) {
hide();
}
show();
} else if (e.getCode() == KeyCode.BACK_SPACE && getEditor().getText().length() == 0) {
clearSelection();
hide();
show();
}
}
private void filterSelectionList() {
setFilteredItems();
setCaretPositionToEnd();
}
private void setFilteredItems() {
filteredItems.setPredicate(item ->
item.toString().toLowerCase().startsWith(getEditor().getText().toLowerCase()));
}
private void setCaretPositionToEnd() {
getEditor().selectEnd();
getEditor().deselect();
}
public void setInitItems(ObservableList<T> values) {
filteredItems = new FilteredList<>(values);
sortedItems = new SortedList<>(filteredItems);
setItems(this.sortedItems);
}
public void clearSelection() {
getSelectionModel().clearSelection();
getEditor().clear();
if (this.filteredItems != null) {
this.filteredItems.setPredicate(item -> true);
}
}
public T getSelectedItem() {
T selectedItem = null;
if (getSelectionModel().getSelectedIndex() > -1) {
selectedItem = getItems().get(getSelectionModel().getSelectedIndex());
}
return selectedItem;
}
public void select(String value) {
if (!value.isEmpty()) {
getItems().stream()
.filter(item -> value.equals(item.toString()))
.findFirst()
.ifPresent(item -> getSelectionModel().select(item));
}
}
}
控件工作正常,直到我将某些内容绑定到Selected或Value属性。一旦发生这种情况,一旦我尝试离开现场(或点击输入导致动作发生),我得到以下异常。 (简单地用鼠标选择值没有问题)我在这个控件中绑定的是CodeTableValue的简单对象 - 它有两个属性 - 字符串,代码和放大器。值。 toString返回Value属性。
如果刚刚设置了没有任何监听器的控件,它就可以正常工作。但是只要我听到其中一个属性的值就失败了。
当我在班上使用AutoCompleteComboBox
控件时:
@FXML private AutoCompleteComboBox<CodeTableValue> myAutoCompleteBox;
myAutoCompleteBox.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> {
System.out.println(nv);
});
引发的异常:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.String cannot be cast to cache.CodeTableValue
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:74)
at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.SelectionModel.setSelectedItem(SelectionModel.java:102)
at javafx.scene.control.ComboBox.lambda$new$152(ComboBox.java:249)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:105)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.ComboBoxBase.setValue(ComboBoxBase.java:150)
at com.sun.javafx.scene.control.skin.ComboBoxPopupControl.setTextFromTextFieldIntoComboBoxValue(ComboBoxPopupControl.java:405)
at com.sun.javafx.scene.control.skin.ComboBoxPopupControl.lambda$new$291(ComboBoxPopupControl.java:82)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:72)
at javafx.scene.Node$FocusedProperty.notifyListeners(Node.java:7718)
at javafx.scene.Node.setFocused(Node.java:7771)
at javafx.scene.Scene$KeyHandler.setWindowFocused(Scene.java:3932)
at javafx.scene.Scene$KeyHandler.lambda$new$11(Scene.java:3954)
at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:137)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:72)
at javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:103)
at javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:110)
at javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:144)
at javafx.stage.Window.setFocused(Window.java:439)
at com.sun.javafx.stage.WindowPeerListener.changedFocused(WindowPeerListener.java:59)
at com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:100)
at com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:40)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassWindowEventHandler.lambda$handleWindowEvent$423(GlassWindowEventHandler.java:150)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassWindowEventHandler.handleWindowEvent(GlassWindowEventHandler.java:148)
at com.sun.glass.ui.Window.handleWindowEvent(Window.java:1266)
at com.sun.glass.ui.Window.notifyFocus(Window.java:1245)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
如果editableFlag为false,则此方法有效。当我们允许用户键入组合框以过滤内容时,就会发生这种情况。没有绑定,我们没有例外,并且值已正确设置。
我一直在深入研究这个异常,它正在抛出:
com.sun.javafx.binding.ExpressionHelper.Generic.fireValueChangedEvent()
if (curChangeSize > 0) {
final T oldValue = currentValue;
currentValue = observable.getValue();
final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
if (changed) {
for (int i = 0; i < curChangeSize; i++) {
try {
curChangeList[i].changed(observable, oldValue, currentValue);
} catch (Exception e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
}
}
只有在绑定了valueProperty或selectedItemProperty时才会出现。否则我们不会遇到这个问题。
javafx.beans.property.ObjectPropertyBase.set(T) @Override
public void set(T newValue) {
if (isBound()) {
throw new java.lang.RuntimeException((getBean() != null && getName() != null ?
getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");
}
if (value != newValue) {
value = newValue;
markInvalid();
}
}
我试图将StringConverter添加到编辑器中以查看是否可以解决问题,但同样,它会继续抛出此异常。也许我们试图处理这一切都错了?
基本上我们希望在用户输入字段时过滤组合框选择项。如果有不同的方式我们应该处理这个,请告诉我,但目前,我认为这可能是JDK中的一个错误?如果字段没有绑定,那么我们没有问题,但是一旦绑定,当字段失去焦点或按下回车键时会发生此异常。
答案 0 :(得分:1)
我使用您的控件创建了一个快速示例:
@Override
public void start(Stage primaryStage) {
AutoCompleteComboBox<CodeTableValue> myAutoCompleteBox =
new AutoCompleteComboBox<>();
myAutoCompleteBox.setInitItems(FXCollections.observableArrayList(new CodeTableValue("One", "1"),
new CodeTableValue("Two", "2"), new CodeTableValue("Three", "4")));
StackPane root = new StackPane(myAutoCompleteBox);
myAutoCompleteBox.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> {
System.out.println(nv);
});
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
基于简单的模型类:
public class CodeTableValue {
private String code;
private String value;
public CodeTableValue(String code, String value) {
this.code = code;
this.value = value;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "code=" + code + ", value=" + value;
}
}
我可以在完成输入后重现您的异常,然后单击Enter以提交值并离开控件:
java.lang.ClassCastException: java.lang.String cannot be cast to CodeTableValue
异常来自以下事实:在编辑模式下从ComboBox中使用的TextField
将始终返回String
,但您强制自定义ComboBox使用CodeTableValue
类。
解决方案只是提供一种使用CodeTableValue
在String和StringConverter
之间进行转换的方法。
所以我修改了你的控件:
public AutoCompleteComboBox(StringConverter<T> converter) {
setEditable(true);
setOnKeyReleased(e -> handleOnKeyReleasedEvent(e));
setOnMouseClicked(e -> handleOnMouseClicked(e));
super.setConverter(converter);
}
现在在样本中:
AutoCompleteComboBox<CodeTableValue> myAutoCompleteBox =
new AutoCompleteComboBox<>(new StringConverter<CodeTableValue>() {
@Override
public String toString(CodeTableValue object) {
if (object != null) {
return object.getValue();
}
return null;
}
@Override
public CodeTableValue fromString(String string) {
return new CodeTableValue(string, string);
}
});
现在可以使用ClassCastException
。
显然,您必须提供一种键入字符串的方法(code
或value
)并从中检索另一个字符串。