it-swarm.com.ru

Ручной ввод текста в JavaFX Spinner не обновляет значение (если пользователь не нажимает клавишу ВВОД)

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

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

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

Это странное поведение, кажется, идет вразрез с соглашением об управлении вращением графического интерфейса пользователя.

21
James Wierzba

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

Таким образом, мы должны вручную зафиксировать значение в слушателе для focusProperty. С другой стороны, у Spinner уже есть код, который делает это - он закрытый, но мы должны его зашифровать.

/**
 * 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 для проверки . Может быть, какой-то внутренний слушатель отсутствует или спиннер еще не обновлен до сравнительно нового API - не копал, хотя.


Обновление

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

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

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

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

26
kleopatra

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

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

Вот улучшенный вариант решения Серджио.

Метод initialize присоединяет код Серхио ко всем спиннерам в контроллере.

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

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

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

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

3
purring pigeon

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

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

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

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