Определите, происходит ли изменение JSpinner от ввода значения или нажатия на стрелку

Когда я получаю ChangeEvent от своего JSpinner, я хотел бы определить, использовал ли пользователь стрелки для увеличения/уменьшения числового значения или напрямую набрал новое число.

Что было бы лучшим подходом для этого?

Обновлено: на данный момент мое ненадежное решение состоит в том, чтобы просто сохранить последнее измененное значение JSpinner, и если новое значение изменения +/- равно значению шага, то я предполагаю, что пользователь щелкнул стрелку. Это работает, за исключением случаев, когда пользователь ввел значение, равное (oldValue +/- step).

Обновлено: почему? Я хочу воспроизвести поведение, обнаруженное в редакторах Midi нескольких известных DAW. JSpinner представляет скорость (0-127) выбранных нот. Он показывает скорость первой выбранной ноты. Обычно ноты различаются по скорости. Когда вы увеличиваете с помощью стрелки, вы хотите увеличить все выбранные ноты на одинаковую величину. Когда вы вводите новое значение, вы хотите, чтобы все скорости были сброшены на это значение.

1. Не используйте KeyListener. 2. Вы можете использовать привязки клавиш. 3. Почему вы хотите это сделать?

Hovercraft Full Of Eels 06.01.2023 23:34

Хм, или, может быть, вы не можете использовать привязки клавиш ... Я еще не заставил это работать.

Hovercraft Full Of Eels 07.01.2023 00:26

ChangeEvent не имеет этой информации. Почему вы решили, что вам нужно это знать?

camickr 07.01.2023 01:10

почему? ..........

kleopatra 07.01.2023 01:50

Чтобы ответить на вопрос в заголовке, расширьте JSpinner, ChangeListener и ChangeEvent, чтобы включить информацию, которую вы ищете. Удачи.

Gilbert Le Blanc 07.01.2023 14:23

Ответил почему. @GilbertLeBlanc да, но моя проблема в том, как получить информацию, которая будет помещена в расширенное событие ChangeEvent.

jjazzboss 07.01.2023 22:30

Изучите исходный код класса JSpinner и узнайте, что вам нужно изменить.

Gilbert Le Blanc 07.01.2023 22:47

Теперь, когда вы упомянули, почему, возможно, сработает использование двух JSpinners, одного для увеличения и одного для выбора.

Gilbert Le Blanc 08.01.2023 00:46
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
8
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Различение триггера изменения значения не поддерживается — единственное событие, связанное со значением, инициированное JSpinner, — это ChangeEvent, который не несет никакого состояния, кроме источника. Нам нужен другой тип слушателя или комбинация слушателей.

Первая мысль: послушать изменения текстового поля редактора, т.е. прослушиватель действий или прослушиватель свойствChangeListener для свойства value. Это не работает, в основном потому, что

  • оба change- и propertyChangeListener запускаются всегда (изменение перед свойством)
  • actionListener не запускается на focusLost

Вторая мысль: копаться в деталях реализации, чтобы зацепиться за действие, запускаемое кнопками со стрелками.

Идея:

  • найдите действие для увеличения/уменьшения из actionMap счетчика: это то же самое, что и действия стрелок, а также используется клавишами вверх/вниз (что я предполагаю, не считая «редактирования»)
  • для каждого создайте обертку, которая устанавливает флаг перед делегированием супер
  • поместите эту обертку в actionMap счетчика
  • найдите кнопки со стрелками в дочерних элементах счетчика и замените их соответствующий actionListener оболочкой

Клиентский код изменит changeListener твика, чтобы он действовал в соответствии с флагом по мере необходимости.

Некоторый код, выполняющий настройку (осторожно: формально не тестировался!):

public static class SpinnerTweaker {

    private JSpinner spinner;
    private boolean wasButtonAction;
    private Object oldValue;

    public SpinnerTweaker(JSpinner spinner) {
        this.spinner = spinner;

        AbstractAction incrementDelegate = createDelegate("increment");
        spinner.getActionMap().put("increment", incrementDelegate);

        AbstractAction decrementDelegate = createDelegate("decrement");
        spinner.getActionMap().put("decrement", decrementDelegate);

        // replacing arrow button's action
        Component[] components = spinner.getComponents();
        for (Component component : components) {
            if (component instanceof JButton) {
                if (component.getName() == "Spinner.nextButton") {
                    ActionListener[] actions = ((JButton) component).getActionListeners();
                    ActionListener uiAction = actions[0];
                    ((JButton) component).removeActionListener(uiAction);
                    ((JButton) component).addActionListener(incrementDelegate);
                }
                if (component.getName() == "Spinner.previousButton") {
                    ActionListener[] actions = ((JButton) component).getActionListeners();
                    ActionListener uiAction = actions[0];
                    ((JButton) component).removeActionListener(uiAction);
                    ((JButton) component).addActionListener(decrementDelegate);
                }
            }
        }

        spinner.addChangeListener(e -> {
            if (wasButtonAction) {
                System.out.println("value changed by button: " + spinner.getValue());
            } else {
                System.out.println("value changed by editing: " + spinner.getValue());
            }
            wasButtonAction = false;
       });

    }

    protected AbstractAction createDelegate(String actionCommand) {
        // hooking into original button action to set button flag
        AbstractAction action = (AbstractAction) spinner.getActionMap().get(actionCommand);
        AbstractAction delegate = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                oldValue = spinner.getValue();
                wasButtonAction = true;
                action.actionPerformed(e);
                // hit min/max - TBD: needs testing!
                if (oldValue.equals(spinner.getValue())) {
                    wasButtonAction = false;
                }
            }
        };

        return delegate;
    }
}

Протестировал, работает. Только один сюрприз: например. значение=2. Вы вводите 4, затем прямо нажимаете стрелку вверх (не нажимайте ввод). Значение становится равным 5, если wasButton=false. Что не совсем неправильно, но неожиданно для меня.

jjazzboss 08.01.2023 18:12

хм.. в этом случае тоже можно было бы ожидать, что wasButton true, звучит как неполная !логика в этом твике - завтра постараюсь найти дыру

kleopatra 08.01.2023 19:32
Ответ принят как подходящий

Решение @kleopatra работает, но я нашел более простое решение.

Хитрость в том, что commitEdit() вызывается JSpinner только внутри, когда изменение является результатом действия увеличения или уменьшения.

public class Spinner2 extends JSpinner
{
    // To be checked by ChangeListener after receiving the ChangeEvent
    public boolean wasManualEdit=true;

    @Override
    public void commitEdit() throws ParseException
    {
        wasManualEdit = false;
        super.commitEdit();
    }

    @Override
    protected void fireStateChanged()
    {
        super.fireStateChanged();
        wasManualEdit = true;
    }
}

Другие вопросы по теме