2015-09-01 7 views
18

Sembra che il controllo Spinner non aggiorni un valore digitato manualmente finché l'utente non preme esplicitamente enter. Quindi, potrebbero digitare un valore (non premere invio) per uscire dal controllo e inviare il modulo, e il valore visualizzato nella casella non è il valore dello Spinner, è il vecchio valore.Digitare manualmente il testo in JavaFX Spinner non sta aggiornando il valore (a meno che l'utente non preme INVIO)

La mia idea era di aggiungere un ascoltatore all'evento di messa a fuoco persa, ma non riesco a vedere un modo per accedere al valore digitato?

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

Questo è un comportamento strano, sembra andare contro la convenzione di un controllo filatore GUI.

risposta

19

Purtroppo, Spinner non si comporta come previsto: nella maggior parte del sistema operativo, dovrebbe commettere il valore modificato a fuoco perduto. Ancora più sfortunato, non fornisce alcuna opzione di configurazione per renderlo facilmente come previsto.

Quindi dobbiamo impegnare manualmente il valore in un listener in focusedProperty. Sul lato positivo, Spinner ha già il codice così facendo - è privata, però, dobbiamo 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); 
}); 

Si noti che c'è un metodo

textField.commitValue() 

cui mi sarei aspettato di. .. beh ... commetti il ​​valore, che non ha alcun effetto. È (finale!) Implementato per aggiornare il valore del textFormatter se disponibile. Non funziona nello Spinner, anche se si utilizza un textFormatter for validation. Potrebbe esserci qualche ascoltatore interno mancante o lo spinner non ancora aggiornato alla relativamente nuova API - non scavare, però.


Aggiornamento

Durante la riproduzione in giro un po 'più con TextFormatter ho notato che un formattatore guarantees to commit su focusLost:

Il valore viene aggiornato quando il controllo perde la sua messa a fuoco o si tratta commited (TextField only)

Che effettivamente funziona come d ocumented tale che potremmo aggiungere un listener ValueProperty del formattatore a ricevere la notifica ogni volta che il valore è impegnata:

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

Trigger per un impegnano:

  • utente preme INVIO
  • perde il controllo si concentrano
  • field.setText è chiamato a livello di codice (questo è un comportamento non documentato!)

Tornando allo spinner: possiamo usare questo comportamento commit-on-focusLost del valore di un formattatore per forzare un commit sul valore di spinnerFactory. Qualcosa di simile

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

Si noti che la modifica (la scrittura o la programmazione sostituendo/aggiungendo/incolla di testo) non non grilletto un commettere - quindi questo non può essere utilizzato se è necessario impegnarsi-on-text-cambiamento.

+2

Questa è una risposta davvero buona e pulita – Alberto

+0

Per coloro che vengono a questa risposta alla ricerca del motivo per cui la loro selezione non si aggiorna nonostante la pressione di invio: probabilmente stai usando un ChangeListener - si attiva solo se c'è stato un vero cambiamento nel valore , a volte JavaFX sembra sbagliarsi. Prova a utilizzare un InvalidationListener. – oskopek

1

Questo è il comportamento standard per il controllo in base alla documentazione:

La proprietà modificabile viene utilizzata per specificare se l'input dell'utente è in grado di essere digitato nell'editor Spinner. Se la modifica è vera, l'input dell'utente sarà ricevuto una volta che l'utente digita e preme il tasto Invio. A questo punto l'input viene passato al convertitore SpinnerValueFactory StringConverter.fromString (String) metodo. Il valore restituito da questa chiamata (di tipo T) viene quindi inviato al metodoSpinnerValueFactory.setValue (Object). Se il valore è valido, lo rimarrà come valore. Se non è valido, il valore factory deve reagire di conseguenza e annullare questa modifica.

Forse potresti utilizzare un evento da tastiera per ascoltare e chiamare il commit di modifica sul controllo mentre procedi.

+2

si inchiodato il doc :-) Ma: come sempre, con gli eventi di basso livello (fi KeyEvent) come trigger per validare/commit testo è una cattiva idea, perché ci sono altri mezzi di input (come ad esempio il fienile) – kleopatra

+0

Si potrebbe provare questo .... spinner.getEditor(). textProperty(). addListener ((osservabile, oldValue, newValue) -> { commitEditorText(); }); –

+0

sì, se si desidera eseguire il commit del valore durante la digitazione/su qualsiasi cambio di testo – kleopatra

0

L'utilizzo di un listener dovrebbe funzionare. È possibile ottenere l'accesso al digitato di valore attraverso l'editore del filatore:

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

Forse hai dimenticato la parte che spiega come farlo? –

+0

La domanda originale mostra come aggiungere il listener, la parte OP non era sicura riguardo a "un modo per accedere al valore digitato" che è mostrato nella mia risposta. – Amber

15

@kleopatra si è diretto nella giusta direzione, ma la soluzione di copia-incolla si è rivelata scomoda e quella basata su TextFormatter non ha funzionato affatto per me. Quindi, ecco uno più corto, che costringe Spinner chiamare è commitEditorText privato() come desiderato:

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

Grazie! Funziona come un fascino. – AvaLanCS

+0

bella risposta. Lavorato – Roger

0

Ecco una variante migliorata della soluzione di Sergio.

Il metodo di inizializzazione allegherà il codice di Sergio a tutti gli Spinners nel controller.

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

Io uso un approccio alternativo: aggiornalo dal vivo mentre digiti. Questo è il mio attuale implementazione:

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