2016-04-11 46 views
6

我有一個問題,如果編輯器中的JavaFX 8 Spinner導致未捕獲NullPointerException如果清除編輯器文本並提交,然後單擊增量或減量按鈕。這是 j8u60 j8u77。有一些運氣,增加/減少按鈕將陷入壓低狀態,並且NPE繼續流動鎖定應用程序。JavaFX微調器空文本nullpointerexception

下面的代碼重新問題對我來說:

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

運行它,清除文本,按Enter鍵(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) 
+0

這不是一個錯誤。它是一個整數微調器,它是一個非整數值,放在它裏面。如果它們是有效整數,它只能旋轉這些值。所以,這將是預期的行爲。您需要在代碼中處理這種潛在的異常。只需將Editable設置爲false,即可刪除更改該值的功能。 – ManoDestra

+2

@ManoDestra查看堆棧跟蹤。沒有必要去捕捉它。它完全在JavaFX內部。 – VGR

+0

因爲上面的代碼沒有處理控件的事件。但它仍然是可編輯的。如果您不希望發生異常,請將可編輯設置爲false,或者處理控件事件(如果允許編輯它)。簡單:) – ManoDestra

回答

3

我有一個翻找通過JDK源。

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

想必newValuenull和自動拆箱:

的NPE從if (newValue < getMin()) {聽者拉姆達扔在這裏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);這大概是空的。此時我相信微調班應檢查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此避免使用無效數據觸發其他監聽器。然而,這突出了Spinner類中的另一個問題。雖然上述方法通過按回車鍵返回有效值來解決問題。在不提交輸入(按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)); 
} 

則行爲是一致的,並給編輯一個機會來處理輸入不言而喻的價值方可出廠。

1

我會認爲這是一個錯誤:在IntegerSpinnerValueFactory應妥善處理這種情況。

一個解決辦法是提供一個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(); 
    } 
} 
+0

雖然工作,有些情況下,默認值不適用(或可能甚至沒有在微調值的域)。 –

+0

在這種情況下,您可以提供價值工廠的實現...只需看看我是否可以完成這項工作 –

3

這是基於整數的微調控件的正確預期行爲。

如果您不希望用戶編輯通過Factory設置的值,則應該將其Editable屬性設置爲false。

或者你應該處理由微調控制器的值屬性引發的事件。

這裏有一個如何做這樣一個簡單的例子:

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

This也可以幫助你,如果你想使用一個轉換器類中更全面地應對變革,以幫助。

另請參閱有關setEditable method的官方文檔;

+0

我實際上將您鏈接的文檔解釋得有些不同;特別是如果價值工廠無效,它應該否決更改。使用偵聽器將更改恢復爲無效值的解決方案的問題是,值的其他偵聽器將觀察對無效值的更改,然後再更改回來。這打破了控制的語義,並且必須寫入這些監聽器來處理(可能忽略)該情況。 –

+0

可能。我同意,但這是控制運作的方式。當然,你不必恢復價值。這只是一個例子,強調可以處理由無效整數輸入引起的異常,然後您可以選擇如何處理它。價值的逆轉純粹是爲了示範的目的。這不一定是理想的方法,只是一個例子。 OP要求提供「解決方法」。這是一個這樣的例子:) – ManoDestra

+1

是的,這就是說,它看起來像'IntegerSpinnerValueFactory'處理「超出範圍」值使用「恢復到有效」的策略,我也不喜歡。 –