---
category: swing
folder: TimePicker
title: JFormattedTextFieldと増減用JButtonを組み合わせて時間選択コンポーネントを作成する
title-en: Create a time selection component by combining a JFormattedTextField and increment/decrement JButtons
tags: [JFormattedTextField, JButton, JSpinner, Calendar]
author: aterai
pubdate: 2026-03-02T00:36:33+09:00
description: JFormattedTextFieldの上下に増減用JButtonを配置してJSpinner風の時間選択コンポーネントを作成します。
summary-jp: JFormattedTextFieldの上下に増減用JButtonを配置してJSpinner風の時間選択コンポーネントを作成します。
summary-en: Create a JSpinner-style time selection component by placing increment/decrement JButtons above and below a JFormattedTextField.
image: https://drive.google.com/uc?id=101CyjRPsRjX22GM-lMU-HvIAXJ5TBezd
---
* Summary [#summary]
JFormattedTextFieldの上下に増減用JButtonを配置してJSpinner風の時間選択コンポーネントを作成します。
`JFormattedTextField`の上下に増減用`JButton`を配置して`JSpinner`風の時間選択コンポーネントを作成します。
// #en{{Create a `JSpinner`-style time selection component by placing increment/decrement `JButton`s above and below a `JFormattedTextField`.}}

#download(https://drive.google.com/uc?id=101CyjRPsRjX22GM-lMU-HvIAXJ5TBezd)

* Source Code Examples [#sourcecode]
#code(link){{
class AutoRepeatHandler extends MouseAdapter implements ActionListener {
  private final Timer autoRepeatTimer;
  private final JTextComponent view;
  private final int delta;
  private final int min;
  private final int max;
  private JButton arrowButton;

  protected AutoRepeatHandler(JTextComponent view, int delta, int min, int max) {
    super();
    this.view = view;
    this.delta = delta;
    this.min = min;
    this.max = max;
    autoRepeatTimer = new Timer(60, this);
    autoRepeatTimer.setInitialDelay(300);
  }

  public static void adjust(JTextComponent field, int delta, int min, int max) {
    field.requestFocusInWindow();
    int range = max - min + 1;
    int value = Integer.parseInt(field.getText());
    value = (value - min + delta) % range;
    if (value < 0) {
      value += range;
    }
    value += min;
    field.setText(String.format("%02d", value));
  }

  @Override public void actionPerformed(ActionEvent e) {
    Object o = e.getSource();
    if (o instanceof Timer) {
      boolean released = Objects.nonNull(arrowButton) &&
                         !arrowButton.getModel().isPressed();
      if (released && autoRepeatTimer.isRunning()) {
        autoRepeatTimer.stop();
      }
    } else if (o instanceof JButton) {
      arrowButton = (JButton) o;
    }
    adjust(view, delta, min, max);
  }

  @Override public void mousePressed(MouseEvent e) {
    if (SwingUtilities.isLeftMouseButton(e) && e.getComponent().isEnabled()) {
      autoRepeatTimer.start();
    }
  }

  @Override public void mouseReleased(MouseEvent e) {
    autoRepeatTimer.stop();
  }

  @Override public void mouseExited(MouseEvent e) {
    if (autoRepeatTimer.isRunning()) {
      autoRepeatTimer.stop();
    }
  }
}
}}

* Description [#description]
- 左: `JFormattedTextField`+`LocalTime`を使用した時間選択コンポーネント
-- `new MaskFormatter("##:##")`で作成しまマスクを`JFormattedTextField`に設定
-- キー入力での数値変更は`JFormattedTextField#setEditable(false)`不可で、マウスホイールによる時間変更のみ可能
-- 以下のように`:`で区切られた時、分フィールド上でマウスホイールが回転したかをチェックして時間を更新

#code{{
timeField.addMouseWheelListener(e -> {
  boolean isUp = e.getWheelRotation() < 0;
  boolean isHourSide = timeField.viewToModel(e.getPoint()) <= 2;
  adjustTime(isHourSide, isUp);
});
}}

- 右: `JFormattedTextField`+`JButton`を組み合わせた時間選択コンポーネント
-- 増減ボタンの配置を上下に変更した`JSpinner`風の時間選択コンポーネント(`24`時間選択固定)を`JFormattedTextField`+`JButton`で作成
-- 時、分フィールドに別々の`JFormattedTextField`を適用し、増減ボタンもそれぞれの上下に配置して倍の`4`個使用
-- `JSpinner`とは異なり、時、分フィールドに別々の`JFormattedTextField`を適用し、増減ボタンもそれぞれの上下に配置して倍の`4`個使用
-- `MouseWheelListener`もそれぞれ設定するので、時、分フィールドのどちらでマウスホイールが回転したかをチェックする必要がない
-- 時、分フィールドは単独でループするよう設定
--- たとえば`12:59`から分フィールドで`1`増加しても`13:00`ではなく`12:00`に変化する

* Reference [#reference]
- [[JSpinnerでLocalDateTimeを使用する>Swing/SpinnerLocalDateTimeModel]]
- [[JButtonがマウスで押されている間、アクションを繰り返すTimerを設定する>Swing/AutoRepeatTimer]]

* Comment [#comment]
#comment
#comment