web-dev-qa-db-fra.com

Taper manuellement du texte dans JavaFX Spinner ne met pas à jour la valeur (sauf si l'utilisateur appuie sur la touche Entrée)

Il semble que le contrôle Spinner ne mette pas à jour une valeur saisie manuellement tant que l'utilisateur n'a pas explicitement appuyé sur Entrée. Ainsi, ils pourraient saisir une valeur (ne pas appuyer sur Entrée), quitter le contrôle et soumettre le formulaire. La valeur affichée dans le compteur est PAS la valeur du compteur, c'est l'ancienne valeur.

Mon idée était d'ajouter un auditeur à l'événement de focus perdu, mais je ne vois pas le moyen d'obtenir un accès à 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, cela semble aller à l’encontre de la convention du contrôle de la barre d’interface graphique.

21
James Wierzba

Malheureusement, Spinner ne se comporte pas comme prévu: dans la plupart des systèmes d’exploitation, il convient de valider la valeur modifiée sur le focus perdu. Encore plus regrettable, il ne fournit aucune option de configuration lui permettant de se comporter comme prévu. 

Nous devons donc valider manuellement la valeur d'un écouteur dans la propriété FocusProperty. Du côté positif, Spinner a déjà du code pour le faire - il est privé, cependant, nous devons le 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);
});

Notez qu'il y a une méthode 

textField.commitValue()

ce à quoi je m'attendais ... bien ... engageons la valeur, qui n'a aucun effet. Il est (final!) Implémenté pour mettre à jour la valeur de textFormatter si disponible. Ne fonctionne pas dans Spinner, même si vous utilisez un textFormatter pour la validation . Peut-être manque-t-il un auditeur interne ou le casserole qui n'a pas encore été mis à jour pour la nouvelle API, mais pas Dig, cependant.


Mettre à jour

En jouant un peu plus avec TextFormatter, j'ai remarqué qu'un formateur garantit à valider sur focusLost:

La valeur est mise à jour lorsque le contrôle perd son focus ou qu'il est validé (TextField uniquement)

Ce qui fonctionne effectivement comme documenté, de sorte que nous pourrions ajouter un auditeur à la valueProperty du formateur pour être averti chaque fois que la valeur est validé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 d'un commit:

  • l'utilisateur clique sur ENTER
  • le contrôle perd le focus
  • field.setText est appelé par programme (il s'agit d'un comportement non documenté!)

Revenons au spinner: nous pouvons utiliser ce comportement commit-on-focusLost de la valeur d'un formateur pour forcer un commit sur la valeur de 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 la modification (taper ou remplacer/ajouter/coller du texte par programme) déclenche une validation de {not _ _ - elle ne peut donc pas être utilisée si une modification de validation est nécessaire.

26
kleopatra

@kleopatra s'est dirigé dans la bonne direction, mais la solution de copier-coller se sent maladroite et celle basée sur TextFormatter ne fonctionnait pas du tout pour moi. En voici donc un plus court, qui oblige Spinner à appeler sa commande privée commitEditorText () comme souhaité:

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

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

La méthode d'initialisation va associer 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();
        }
    }
}
3
Robert

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

La propriété editable est utilisée pour spécifier si une entrée utilisateur est capable de être tapé dans l'éditeur Spinner. Si editable est true, la saisie de l'utilisateur sera être reçu une fois que l'utilisateur a tapé et appuyé sur la touche Entrée. À ceci pointer l'entrée est transmise au convertisseur SpinnerValueFactory Méthode StringConverter.fromString (String). La valeur renvoyée par cet appel (de type T) est alors envoyé au Méthode SpinnerValueFactory.setValue (Object). Si la valeur est valide, elle restera comme la valeur. S'il n'est pas valide, la valeur factory sera Il faut réagir en conséquence et annuler ce changement.

Vous pourriez peut-être utiliser un événement de clavier pour écouter et appeler le commit d'édition sur le contrôle au fur et à mesure.

3
purring pigeon

Utiliser un auditeur devrait fonctionner. Vous pouvez accéder à la valeur saisie à l'aide de l'éditeur de spinner:

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

J'utilise une autre approche: mettez-la à jour tout en tapant. Ceci est ma mise en œuvre actuelle:

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