web-dev-qa-db-ja.com

JavaFX Spinnerに手動でテキストを入力しても値が更新されない(ユーザーがEnterキーを押さない限り)

ユーザーが明示的にEnterキーを押すまで、Spinnerコントロールは手動で入力した値を更新しないようです。したがって、値を入力して(Enterキーを押すのではなく)コントロールを終了し、フォームを送信することができます。スピナーに表示される値はスピナーの値ではなく、古い値です。

私の考えは、フォーカスを失ったイベントにリスナーを追加することでしたが、入力した値にアクセスする方法がわかりませんか?

spinner.focusedProperty().addListener((observable, oldValue, newValue) -> 
{
    //if focus lost
    if(!newValue)
    {
        //somehow get the text the user typed in?
    }
});

これは奇妙な動作です。GUIスピナーコントロールの規則に反するようです。

25
James Wierzba

残念ながら、Spinnerは期待どおりに動作しません。ほとんどのOSでは、フォーカスが失われたときに編集した値をコミットする必要があります。さらに残念なことに、簡単に期待どおりに動作させるための構成オプションはありません。

したがって、リスナーの値を手動で、focusedPropertyにコミットする必要があります。明るい面としては、Spinnerはすでにそうしているコードを持っています-それはプライベートですが、c&pする必要があります

/**
 * c&p from Spinner
 */
private <T> void commitEditorText(Spinner<T> spinner) {
    if (!spinner.isEditable()) return;
    String text = spinner.getEditor().getText();
    SpinnerValueFactory<T> valueFactory = spinner.getValueFactory();
    if (valueFactory != null) {
        StringConverter<T> converter = valueFactory.getConverter();
        if (converter != null) {
            T value = converter.fromString(text);
            valueFactory.setValue(value);
        }
    }
}

// useage in client code
spinner.focusedProperty().addListener((s, ov, nv) -> {
    if (nv) return;
    //intuitive method on textField, has no effect, though
    //spinner.getEditor().commitValue(); 
    commitEditorText(spinner);
});

メソッドがあることに注意してください

textField.commitValue()

私はそれを期待していました...まあ...値をコミットしますが、効果はありません。利用可能であれば、textFormatterの値を更新するために実装されました(最終!)。 textFormatter for validation を使用しても、Spinnerでは機能しません。内部リスナーが欠落しているか、スピナーが比較的新しいAPIにまだ更新されていない可能性があります-ただし、Digしませんでした。


更新

TextFormatterでもう少し遊んでいるときに、フォーマッタ guaranteesto commit on focusLostに気づきました:

コントロールがフォーカスを失うか、コミットされると、値が更新されます(TextFieldのみ)。

実際に文書化されているとおりに機能するため、フォーマッタのvaluePropertyにリスナーを追加して、値がコミットされるたびに通知を受けることができます。

TextField field = new TextField();
TextFormatter fieldFormatter = new TextFormatter(
      TextFormatter.IDENTITY_STRING_CONVERTER, "initial");
field.setTextFormatter(fieldFormatter);
fieldFormatter.valueProperty().addListener((s, ov, nv) -> {
    // do stuff that needs to be done on commit
} );

コミットのトリガー:

  • ユーザーがEnterキーを押す
  • コントロールがフォーカスを失う
  • field.setTextはプログラムで呼び出されます(これは文書化されていない動作です!)

スピナーに戻ります。フォーマッターの値のこのcommit-on-focusLost動作を使用して、spinnerFactoryの値を強制的にコミットできます。何かのようなもの

// normal setup of spinner
SpinnerValueFactory factory = new IntegerSpinnerValueFactory(0, 10000, 0);
spinner.setValueFactory(factory);
spinner.setEditable(true);
// hook in a formatter with the same properties as the factory
TextFormatter formatter = new TextFormatter(factory.getConverter(), factory.getValue());
spinner.getEditor().setTextFormatter(formatter);
// bidi-bind the values
factory.valueProperty().bindBidirectional(formatter.valueProperty());

編集(テキストの入力またはプログラムによる置換/追加/貼り付けのいずれか)はコミットをトリガーしないので注意してください。したがって、テキスト変更のコミットが必要な場合は使用できません。

29
kleopatra

@kleopatraは正しい方向に向かいましたが、コピーと貼り付けのソリューションは扱いにくいと感じ、TextFormatterベースのソリューションはまったく機能しませんでした。したがって、ここに短いものがあります。これにより、Spinnerは必要に応じてそれをプライベートcommitEditorText()と呼ぶようになります。

spinner.focusedProperty().addListener((observable, oldValue, newValue) -> {
  if (!newValue) {
    spinner.increment(0); // won't change value, but will commit editor
  }
});
25
Sergio

これは、セルジオのソリューションの改良版です。

Initializeメソッドは、コントローラー内のすべてのスピナーにSergioのコードをアタッチします。

public void initialize(URL location, ResourceBundle resources) {
    for (Field field : getClass().getDeclaredFields()) {
        try {
            Object obj = field.get(this);
            if (obj != null && obj instanceof Spinner)
                ((Spinner) obj).focusedProperty().addListener((observable, oldValue, newValue) -> {
                    if (!newValue) {
                        ((Spinner) obj).increment(0);
                    }
                });
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
3
Robert

これは、ドキュメントによると、コントロールの標準的な動作です。

Editableプロパティは、ユーザー入力をスピナーエディターに入力できるかどうかを指定するために使用されます。 editableがtrueの場合、ユーザーがEnterキーを入力して押すと、ユーザー入力が受信されます。この時点で、入力はSpinnerValueFactoryコンバーターのStringConverter.fromString(String)メソッドに渡されます。 (T型の)この呼び出しからの戻り値は、SpinnerValueFactory.setValue(Object)メソッドに送信されます。値が有効な場合、値として残ります。無効な場合、バリューファクトリはそれに応じて対応し、この変更を取り消す必要があります。

おそらく、キーボードイベントを使用して、コントロールの編集コミットをリッスンして呼び出します。

3
purring pigeon

リスナーを使用する必要があります。入力した値にスピナーのエディターからアクセスできます。

spinner.getEditor().getText();
0
Amber

私は別のアプローチを使用しています-入力中にライブで更新します。これは私の現在の実装です:

getEditor().textProperty().addListener { _, _, nv ->
    // let the user clear the field without complaining
    if(nv.isNotEmpty()) {
        Double newValue = getValue()
        try {
            newValue = getValueFactory().getConverter().fromString(nv)
        } catch (Exception e) { /* user typed an illegal character */ } 
        getValueFactory().setValue(newValue)
    }
0
Xerus