La saisie manuelle du texte dans JavaFX Spinner ne met pas à jour la valeur (à moins que l'utilisateur n'appuie sur ENTRER)

il semble que la commande Spinner ne mette pas à jour une valeur saisie manuellement jusqu'à ce que l'utilisateur appuie explicitement sur Entrée. Ainsi, ils peuvent taper une valeur (appuyez pas sur entrée) sortie de contrôle, et de soumettre le formulaire, et la valeur affichée dans la casserole n'est PAS la valeur du Compteur, c'est l'ancienne valeur.

mon idée était d'ajouter un auditeur à l'événement lost focus, mais je ne vois pas de moyen d'accéder à la valeur saisie?

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

C'est un comportement étrange, il semble pour aller à l'encontre de la convention D'un contrôle GUI spinner.

20
demandé sur kleopatra 2015-09-01 23:38:27

6 réponses

malheureusement, Spinner ne se comporte pas comme prévu: dans la plupart des OS, il devrait propager la valeur éditée sur focus lost. Encore plus regrettable, il ne fournit aucune option de configuration pour le faire se comporter facilement comme prévu.

nous devons donc attribuer manuellement la valeur d'un auditeur à la propriété focalisée. Du côté positif, Spinner a déjà le code le faisant - c'est privé, cependant, nous devons c&P it

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

notez qu'il y a une méthode

textField.commitValue()

que je l'aurais imaginé ... bien. .. engager la valeur, qui n'a aucun effet. C'est (final!) implémenté pour mettre à jour la valeur du textFormatter si disponible. Ne fonctionne pas dans le compteur, même si vous utilisez un textFormatter pour la validation. Peut - être qu'il manque un écouteur interne ou que le spinner n'a pas encore été mis à jour vers l'api relativement nouvelle-n'a pas creusé, cependant.


mise à Jour

Tout en jouant autour d'un peu plus avec TextFormatter j'ai remarqué qu'un formateur garanties pour commettre sur focusLost:

La valeur est mise à jour lorsque le contrôle perd son accent ou elle est engagé (TextField)

qui fonctionne en effet comme documenté de sorte que nous pourrions ajouter un auditeur à la propriété valueProperty du formatteur pour être notifié chaque fois que la valeur est engagée:

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

Déclencheurs pour une commit:

  • hits de l'utilisateur ENTER
  • perte de contrôle Mise au point
  • champ.setText est appelé programmatically (c'est un comportement non documenté!)

pour en revenir au spinner: nous pouvons utiliser ce comportement de commit-on-focus de la valeur d'un formatteur pour forcer une commit sur la valeur du spinnerFactory. Quelque chose comme

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

notez que l'édition (en tapant ou en remplaçant/ajoutant/collant le texte par programmation) ne déclencher une propagation-donc cela ne peut pas être utilisé si la propagation est nécessaire.

23
répondu kleopatra 2017-05-23 12:18:11

@kleopatra s'est dirigé dans la bonne direction, mais la solution de copier-coller semble maladroite et celle basée sur la matière textuelle ne fonctionnait pas du tout pour moi. Donc en voici un plus court, ce qui force Spinner à l'appeler son commitedortext privé() comme désiré:

spinner.focusedProperty().addListener((observable, oldValue, newValue) -> {
  if (!newValue) {
    spinner.increment(0); // won't change value, but will commit editor
  }
});
19
répondu Sergio 2016-09-07 22:51:31

c'est un comportement standard pour le contrôle selon la documentation:

la propriété editable est utilisée pour spécifier si l'utilisateur peut entrer être tapé dans la casserole de l'éditeur. Si éditable est true, l'utilisateur input être reçu une fois que l'utilisateur tape et appuie sur la touche Entrée. À ce point où L'entrée est transmise au convertisseur SpinnerValueFactory StringConverter.méthode fromString (String). La valeur retournée de cet appel (de type T) est ensuite envoyé à le SpinnerValueFactory.méthode setValue(Object). Si la valeur est valide, il restera comme la valeur. Si elle est invalide, l'usine de valeur il faut réagir en conséquence et annuler ce changement.

peut-être pourriez-vous utiliser un événement clavier pour écouter et appeler l'édition commit sur le contrôle que vous allez.

2
répondu purring pigeon 2015-09-01 20:50:21

Voici une variante améliorée de la solution de Sergio.

la méthode initialize fixera le code de Sergio à tous les Spinners du contrôleur.

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();
        }
    }
}
2
répondu Robert 2017-03-29 12:33:21

utiliser un écouteur devrait fonctionner. Vous pouvez obtenir l'accès à la valeur tapée par l'éditeur du spinner:

spinner.getEditor().getText();
0
répondu Amber 2015-09-01 21:22:52

j'utilise une approche alternative - mettre à jour en direct tout en tapant. C'est mon actuel de mise en œuvre:

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
répondu Xerus 2017-12-10 19:30:29