2016-04-11 35 views
6

編集可能なJavaFX 8 Spinnerは、編集者のテキストをクリアしてからインクリメントまたはデクリメントボタンをクリックすると、キャッチされないNullPointerExceptionが発生する問題があります。これは j8u60 j8u77です。いくつかの運があれば、インクリメント/デクリメントボタンが押し下げられた状態になり、NPEはアプリケーションをロックアップし続ける。JavaFX Spinner空文字列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(); 
    } 
} 

実行それは、テキストをクリアし、キーを押して(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

。これはIntegerスピナーであり、内部に配置された非整数値です。有効な整数の場合にのみ、値を回転させることができます。したがって、これは予想される動作です。この潜在的な例外をコード内で処理する必要があります。 Editableをfalseに設定するだけで、その値を変更する機能が削除されます。 – ManoDestra

+2

@ManoDestraスタックトレースを見てください。それを捕まえることはできません。これはJavaFXの完全な内部です。 – VGR

+0

上記のコードは、コントロールのイベントを処理するために何もしないためです。それでもまだ編集可能に設定されています。例外を発生させたくない場合は、editableをfalseに設定するか、コントロールイベントを編集できるようにしてください。シンプル:) – ManoDestra

答えて

3

私はJDKのソースを通じて漁りました。

NPEは、ここでリスナーラムダにif (newValue < 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(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と自動アンボクシングで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);。この時点では、スピナークラスはvaluenullではないことをチェックし、以前の値をエディターに復元する必要があると考えています。

これはバグであることがかなり確信しています。さらに、nullを返さないコンバーターを使用した場合の回避策は、問題を隠すだけで、値を変換できないときにどのような値を返さなければならないと思われません。 valuePropertyこの上のリスナーとは対照的に

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

編集:回避策

ポリシー「有効に戻す」で無効な入力を拒否するようにスピナーエディタのonActionの交換は、問題を修正します無効なデータを持つ他のリスナーの起動を防ぎます。しかしこれはスピナークラスの別の問題を強調しています。上記の問題は、enterキーを押して有効な値に戻すことで解決します。コミットせずに入力を消去して(Enterを押す)、増分または減分を押すと、同じNPEが発生しますが、コールスタックはわずかに異なります。

原因:

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

I

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に両方のコール似ていますcommitEditorTextををトリガに変更する必要があると考えてくださいエディタ上代わりのようなので:

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

これは、IntegerベースのSpinnerコントロールの正しい動作です。

ユーザーが工場で設定した値を編集しないようにするには、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()); 
     } 
    } 
} 

Thisも、あなたを助けることができます。

setEditable methodの公式ドキュメントも参照してください。

+0

私は実際にあなたが多少異なる方法でリンクしたドキュメントを解釈します。具体的には、バリュー・ファクトリが無効な場合に変更を拒否すべきです。変更を無効な値に戻すためにリスナーを使用するソリューションの問題は、値の他のリスナーが無効な値への変更を観察し、その後に変更を戻すことです。これはコントロールのセマンティクスを破壊し、それらのリスナーはそのケースを処理する(恐らく無視する)必要があります。 –

+0

おそらく。私は同意しますが、これがコントロールの動作方法です。もちろん、値を元に戻す必要はありません。これは、無効な整数入力によって発生した例外を処理できることを強調するための例に過ぎず、その処理方法を選択することができます。値の復帰は、純粋にデモンストレーション目的のためです。必ずしも理想的なアプローチではなく、単なる例です。 OPは "回避策"を求めた。これはそのような例の1つです:) – ManoDestra

+1

「IntegerSpinnerValueFactory」が「有効範囲に戻す」戦略を使用して「範囲外」値を処理するように見えますが、これは本当に好きではありません。 –

関連する問題