JavaFX Spinner空文本nullpointerexception [英] JavaFX Spinner empty text nullpointerexception

查看:110
本文介绍了JavaFX 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;

公共类测试扩展Application {
public static void main(String [] args){
launch(args);
}

@Override
public void start(阶段aPrimaryStage)抛出异常{
IntegerSpinnerValueFactory valueFactory = new IntegerSpinnerValueFactory(0,10);
Spinner<整数> spinner = new Spinner<>(valueFactory);
spinner.setEditable(true);
aPrimaryStage.setScene(new Scene(spinner));
aPrimaryStage.show();
}
}

运行它,清除文本,按回车键( NullPointerException ),单击递增或递减按钮现在也会导致NPE。



任何人都可以确认这是一个JavaFX错误并提出解决方法吗?



编辑:异常堆栈跟踪

 线程JavaFX应用程序线程中的异常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)
在javafx.scene.control.Spinner.la mbda $ 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.Even tDispatchChainImpl.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.EventDispatchChainIm pl.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.dispa tchCapturingEvent(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)com.sun.j上的
avafx.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)


解决方案

我翻过JDK来源。





NPE从中抛出if(newValue<这里的侦听器lambda中的getMin()){


javafx.scene.control.SpinnerValueFactory.java




  public IntegerSpinnerValueFactory(@NamedArg(min)int min ,
@NamedArg(max)int max,
@NamedArg(initialValue)int initialValue,
@NamedArg(amountToStepBy)int amountToStepBy){
setMin(分钟);
setMax(max);
setAmountToStepBy(amountToStepBy);
setConverter(new IntegerStringConverter());

valueProperty()。addListener((o,oldValue,newValue) - > {
//当设置值时,我们需要做出反应以确保它是
//有效值(如果没有,适当地炸掉)
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){
//如果指定的值为null或零长度,则返回null
if(value == null){
return null;
}

value = value.trim();

if(value.length()< 1){
返回null;
}

返回Integer.valueOf(value);
}

/ ** {@inheritDoc} * /
@Override public String toString(Integer value){
//如果指定的值为null,则返回零长度字符串
if(value == null){
return;
}

return(Integer.toString(((Integer)value)。intValue()));
}
}

我们看到它会愉快地返回 null 表示空字符串,由于输入没有有效值,这是合理的。



跟踪通话stack我找到值的来源:


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值=转换器.fromString(text); 可能是null。此时我认为spinner类应检查是否不是 null ,如果它是恢复之前的值给编辑。



我现在很确定这是一个错误。此外,我不认为使用永远不会返回null的转换器的工作将会正常工作,因为它只会掩盖问题,并且当值无法转换时应该返回什么值?



编辑:解决方法



更换 onAction 使用返回有效策略拒绝无效输入的微调器编辑器修复了该问题:

 公共静态< 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键返回有效值来解决问题。擦除输入而不提交(按下输入)然后按递增或递减将导致相同的NPE,但调用堆栈略有不同。



原因:

  public void increment(int steps){
SpinnerValueFactory< T> valueFactory = getValueFactory();
if(valueFactory == null){
抛出新的IllegalStateException(无法使用null SpinnerValueFactory递增Spinner);
}
commitEditorText();
valueFactory.increment(步骤);
}

减少类似,都调用 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));
}

然后行为将保持一致并为编辑提供处理输入的机会在它进入价值工厂之前。


I have an issue where an editable JavaFX 8 Spinner causes an uncaught NullPointerException if one clears the editor text and commits and then clicks either the increment or decrement button. This is j8u60 j8u77. With some luck the increment/decrement button will get stuck in depressed state and the NPE's keep flowing locking up the application.

The following code reproduces the issue for me:

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();
    }
}

Run it, clear the text, press enter (NullPointerException), clicking either increment or decrement button will now also cause NPE.

Can any one confirm that this is a JavaFX bug and suggest a workaround?

Edit: The exception stack trace

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)

解决方案

I had a rummage through the JDK source.

The NPE is thrown from if (newValue < getMin()) { in the listener lambda here:

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);
    }

presumably newValue is null and the auto unboxing of null throws NPE. As the input comes from the editor, I suspect the IntegerStringConverter which is the default converter.

Looking at the implementation here:

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()));
    }
}

We see that it will happily return null for the empty string, which is kind of reasonable given that there exists no valid value for the input.

Tracing up the call stack I find where the value is coming from:

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);
            }
        }
    });

The value is set with the value obtained from the converter T value = converter.fromString(text); which presumably is null. At this point I believe that the spinner class should check that value is not null and if it is restore the previous value to the editor.

I am now fairly sure that this is a bug. Moreover I don't think that a work around with a converter that never returns null is going to work properly as it will only mask the problem and what value should be returned when the value cannot be converted?

Edit: Workaround

Replacing the onAction of the spinner editor to reject invalid input with a "return to valid" policy fixes the issue:

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();
    });
}

As opposed to a listener on the valueProperty this avoids triggering other listeners with invalid data. However this highlights another issue in the spinner class. While the above fixes the issue by returning to a valid value on pressing enter. Erasing the input without committing (pressing enter) and then pressing increment or decrement will cause the same NPE but with slightly different call stack.

Cause:

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);
}

Decrement is similar, both call into commitEditorText below:

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);
        }
    }
}

Notice the copy-paste from the onAction in the constructor:

    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);
            }
        }
    });

I believe that commitEditorText should be changed to trigger onAction on the editor instead like so:

private void commitEditorText() {
    if (!isEditable()) return;
    getEditor().getOnAction().handle(new ActionEvent(this, this));
}

then the behavior would be consistent and give the editor a chance to handle the input before it goes to the value factory.

这篇关于JavaFX Spinner空文本nullpointerexception的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆