Вручную ввод текста в JavaFX Spinner не обновляет значение (если пользователь не нажимает ENTER)

Кажется, что элемент управления Spinner не обновляет введенное вручную значение, пока пользователь явно не нажат на ввод. Таким образом, они могут ввести значение (не нажать enter) выйти из элемента управления и отправить форму, а значение, отображаемое в счетчике, НЕ является значением Spinner, это старое значение.

Моя идея состояла в том, чтобы добавить слушателя к событию потерянного фокуса, но я не вижу способа получить доступ к введенному значению?

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

Это странное поведение, похоже, противоречит соглашению управления счетчиками GUI.

Ответы

Ответ 1

К сожалению, Spinner не ведет себя так, как ожидалось: в большинстве ОС он должен зафиксировать отредактированное значение при утере фокуса. Еще более неудачно, он не предоставляет какой-либо опции конфигурации, чтобы заставить его вести себя так, как ожидалось.

Итак, мы должны вручную зафиксировать значение в слушателе для focusProperty. С яркой стороны у Spinner уже есть код, который делает это - он частный, однако мы должны 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);
});

Обратите внимание, что существует способ

textField.commitValue()

который я ожидал бы... ну... зафиксировать значение, которое не имеет никакого эффекта. Он (final!) Реализован для обновления значения textFormatter, если он доступен. Не работает в Spinner, даже если вы используете textFormatter для проверки. Может быть, какой-то внутренний прослушиватель отсутствует, или счетчик, еще не обновленный до относительно нового api, - не копал, хотя.


Обновление

Во время воспроизведения немного больше с TextFormatter я заметил, что форматтер гарантирует фиксацию на focusLost:

Значение обновляется, когда элемент управления теряет фокус или он совершается (только для TextField)

Что действительно работает так документировано, что мы могли бы добавить слушателя к formatter 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());

Обратите внимание, что редактирование (ввод или программная замена/добавление/вставка текста) не вызывает фиксацию - поэтому это не может быть использовано, если требуется фиксация текста в тексте.

Ответ 2

@kleopatra направляется в правильном направлении, но решение для копирования-вставки кажется неудобным, а метод TextFormatter не работает для меня вообще. Итак, здесь короче, что заставляет Spinner называть его private commitEditorText() по желанию:

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

Ответ 3

Это стандартное поведение для элемента управления в соответствии с документацией:

Измененное свойство используется для указания, может ли пользовательский ввод быть введенным в редактор Spinner. Если редактируемое верно, пользовательский ввод будет получать, как только пользователь вводит и нажимает клавишу Enter. В этот точка вводится в преобразователь SpinnerValueFactory StringConverter.fromString(String). Возвращаемое значение из этот вызов (типа T) затем отправляется на Метод SpinnerValueFactory.setValue(Object). Если значение действительно, оно останется в качестве значения. Если оно недействительно, значение factory будет необходимо соответствующим образом отреагировать и отменить это изменение.

Возможно, вы могли бы использовать событие клавиатуры для прослушивания и вызывать фиксацию редактирования в элементе управления, когда вы идете.

Ответ 4

Использование слушателя должно работать. Вы можете получить доступ к введенному значению через редактор spinner:

spinner.getEditor().getText();

Ответ 5

Вот улучшенный вариант решения 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();
        }
    }
}

Ответ 6

Я использую альтернативный подход - обновляйте его в реальном времени при наборе текста. Это моя текущая реализация:

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