![](/img/trans.png)
[英]Android NullPointerException When retriving Spinner text
[英]JavaFX Spinner empty text nullpointerexception
我有一個問題,如果清除編輯器文本並提交,然后單擊遞增或遞減按鈕,可編輯的 JavaFX 8 Spinner
會導致未捕獲的NullPointerException
。 這是
j8u60
j8u77。 運氣好的話,遞增/遞減按鈕會卡在按下狀態,並且 NPE 會不斷鎖定應用程序。
以下代碼為我重現了該問題:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory;
import javafx.stage.Stage;
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage aPrimaryStage) throws Exception {
IntegerSpinnerValueFactory valueFactory = new IntegerSpinnerValueFactory(0, 10);
Spinner<Integer> spinner = new Spinner<>(valueFactory);
spinner.setEditable(true);
aPrimaryStage.setScene(new Scene(spinner));
aPrimaryStage.show();
}
}
運行它,清除文本,按回車( NullPointerException
),單擊增量或減量按鈕現在也會導致 NPE。
任何人都可以確認這是一個 JavaFX 錯誤並提出解決方法嗎?
編輯:異常堆棧跟蹤
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at javafx.scene.control.SpinnerValueFactory$IntegerSpinnerValueFactory.lambda$new$215(SpinnerValueFactory.java:475)
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.SpinnerValueFactory.setValue(SpinnerValueFactory.java:150)
at javafx.scene.control.Spinner.lambda$new$210(Spinner.java:139)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at com.sun.javafx.scene.control.behavior.TextFieldBehavior.fire(TextFieldBehavior.java:179)
at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:178)
at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at com.sun.javafx.scene.control.skin.SpinnerSkin.lambda$new$473(SpinnerSkin.java:151)
at com.sun.javafx.event.CompositeEventHandler$NormalEventFilterRecord.handleCapturingEvent(CompositeEventHandler.java:282)
at com.sun.javafx.event.CompositeEventHandler.dispatchCapturingEvent(CompositeEventHandler.java:98)
at com.sun.javafx.event.EventHandlerManager.dispatchCapturingEvent(EventHandlerManager.java:223)
at com.sun.javafx.event.EventHandlerManager.dispatchCapturingEvent(EventHandlerManager.java:180)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchCapturingEvent(CompositeEventDispatcher.java:43)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:52)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:197)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:147)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:228)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:227)
at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
at com.sun.glass.ui.View.notifyKey(View.java:966)
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)
這是基於Integer的Spinner控件的正確和預期行為。
如果您不希望用戶編輯通過Factory設置的值,則應將其Editable屬性設置為false。
或者您應該處理由微調器的value屬性引發的事件。
以下是如何執行此操作的簡單示例:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory;
import javafx.stage.Stage;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class Spin extends Application {
Spinner<Integer> spinner;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage aPrimaryStage) throws Exception {
IntegerSpinnerValueFactory valueFactory = new IntegerSpinnerValueFactory(0, 10);
spinner = new Spinner<>(valueFactory);
spinner.setEditable(true);
spinner.valueProperty().addListener((observableValue, oldValue, newValue) -> handleSpin(observableValue, oldValue, newValue));
aPrimaryStage.setScene(new Scene(spinner));
aPrimaryStage.show();
}
private void handleSpin(ObservableValue<?> observableValue, Number oldValue, Number newValue) {
try {
if (newValue == null) {
spinner.getValueFactory().setValue((int)oldValue);
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
如果您希望使用轉換器類來幫助更全面地處理更改, 這也可以幫助您。
另請參閱有關setEditable方法的官方文檔;
我通過JDK源進行了翻找。
NPE是從if (newValue < getMin()) {
在監聽器lambda中拋出的:
javafx.scene.control.SpinnerValueFactory.java
public IntegerSpinnerValueFactory(@NamedArg("min") int min,
@NamedArg("max") int max,
@NamedArg("initialValue") int initialValue,
@NamedArg("amountToStepBy") int amountToStepBy) {
setMin(min);
setMax(max);
setAmountToStepBy(amountToStepBy);
setConverter(new IntegerStringConverter());
valueProperty().addListener((o, oldValue, newValue) -> {
// when the value is set, we need to react to ensure it is a
// valid value (and if not, blow up appropriately)
if (newValue < getMin()) {
setValue(getMin());
} else if (newValue > getMax()) {
setValue(getMax());
}
});
setValue(initialValue >= min && initialValue <= max ? initialValue : min);
}
想必newValue
是null
和自動拆箱null
,拋出NPE。 由於輸入來自編輯器,我懷疑IntegerStringConverter
是默認轉換器。
看看這里的實現:
javafx.util.converter.IntegerStringConverter
public class IntegerStringConverter extends StringConverter<Integer> {
/** {@inheritDoc} */
@Override public Integer fromString(String value) {
// If the specified value is null or zero-length, return null
if (value == null) {
return null;
}
value = value.trim();
if (value.length() < 1) {
return null;
}
return Integer.valueOf(value);
}
/** {@inheritDoc} */
@Override public String toString(Integer value) {
// If the specified value is null, return a zero-length String
if (value == null) {
return "";
}
return (Integer.toString(((Integer)value).intValue()));
}
}
我們看到它將為空字符串高興地返回null
,這是合理的,因為輸入沒有有效值。
跟蹤調用堆棧我發現值的來源:
javafx.scene.control.Spinner
public Spinner() {
getStyleClass().add(DEFAULT_STYLE_CLASS);
setAccessibleRole(AccessibleRole.SPINNER);
getEditor().setOnAction(action -> {
String text = getEditor().getText();
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory != null) {
StringConverter<T> converter = valueFactory.getConverter();
if (converter != null) {
T value = converter.fromString(text);
valueFactory.setValue(value);
}
}
});
使用從轉換器T value = converter.fromString(text);
獲得的值設置該值T value = converter.fromString(text);
大概是空的。 此時我認為spinner類應檢查該value
是否為null
以及是否將以前的值恢復到編輯器。
我現在相當確定這是一個錯誤。 此外,我不認為使用永遠不會返回null的轉換器的工作將會正常工作,因為它只會掩蓋問題,並且當值無法轉換時應該返回什么值?
使用“返回有效”策略替換微調器編輯器的onAction
以拒絕無效輸入可修復此問題:
public static <T> void fixSpinner2(Spinner<T> aSpinner) {
aSpinner.getEditor().setOnAction(action -> {
String text = aSpinner.getEditor().getText();
SpinnerValueFactory<T> factory = aSpinner.getValueFactory();
if (factory != null) {
StringConverter<T> converter = factory.getConverter();
if (converter != null) {
T value = converter.fromString(text);
if (null != value) {
factory.setValue(value);
}
else {
aSpinner.getEditor().setText(converter.toString(factory.getValue()));
}
}
}
action.consume();
});
}
與valueProperty
上的偵聽器相反,這避免了使用無效數據觸發其他偵聽器。 然而,這突出了微調器類中的另一個問題。 雖然上面通過按Enter鍵返回有效值來解決問題。 擦除輸入而不提交(按Enter鍵)然后按遞增或遞減將導致相同的NPE,但調用堆棧略有不同。
原因:
public void increment(int steps) {
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory == null) {
throw new IllegalStateException("Can't increment Spinner with a null SpinnerValueFactory");
}
commitEditorText();
valueFactory.increment(steps);
}
遞減類似,都調用下面的commitEditorText
:
private void commitEditorText() {
if (!isEditable()) return;
String text = getEditor().getText();
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory != null) {
StringConverter<T> converter = valueFactory.getConverter();
if (converter != null) {
T value = converter.fromString(text);
valueFactory.setValue(value);
}
}
}
注意構造函數中onAction
的復制粘貼:
getEditor().setOnAction(action -> {
String text = getEditor().getText();
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory != null) {
StringConverter<T> converter = valueFactory.getConverter();
if (converter != null) {
T value = converter.fromString(text);
valueFactory.setValue(value);
}
}
});
我相信應該更改commitEditorText
以在編輯器上觸發onAction
,如下所示:
private void commitEditorText() {
if (!isEditable()) return;
getEditor().getOnAction().handle(new ActionEvent(this, this));
}
然后行為將是一致的,並讓編輯器有機會在輸入到值工廠之前處理輸入。
我認為這是一個錯誤: IntegerSpinnerValueFactory
應該正確處理這種情況。
一種解決方法是為微調器值工廠提供converter
器,如果文本值無效,則將其converter
為默認值:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage aPrimaryStage) throws Exception {
IntegerSpinnerValueFactory valueFactory = new IntegerSpinnerValueFactory(0, 10);
valueFactory.setConverter(new StringConverter<Integer>() {
@Override
public String toString(Integer object) {
return object.toString() ;
}
@Override
public Integer fromString(String string) {
if (string.matches("-?\\d+")) {
return new Integer(string);
}
// default to 0:
return 0 ;
}
});
Spinner<Integer> spinner = new Spinner<>(valueFactory);
spinner.setEditable(true);
aPrimaryStage.setScene(new Scene(spinner));
aPrimaryStage.show();
}
}
這是一個已知的錯誤,已在Java 9中修復 - 請參閱https://bugs.openjdk.java.net/browse/JDK-8150962
另一個對我有用的答案,阻止你輸入任何非數字值:
spinnerIndex.editorProperty().getValue().textProperty().addListener(new ChangeListener<String>() {
private static boolean isInteger(final String s) {
try {
@SuppressWarnings("unused")
int d = Integer.parseInt(s);
return true;
}
catch (NumberFormatException e) {
return false;
}
}
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!isInteger(newValue)) {
final StringProperty sp = (StringProperty)observable;
sp.set(oldValue);
}
}
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.