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

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

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

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

Это странное поведение, он кажется, идет вразрез с Конвенцией управления GUI spinner.

6 ответов


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

поэтому мы должны вручную зафиксировать значение в прослушивателе в focusedProperty. С другой стороны, у 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()

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


обновление

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

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

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

триггеры для a commit:

  • пользователь нажимает ENTER
  • элемент управления теряет фокус

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

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

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

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

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


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

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

spinner.getEditor().getText();

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

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