• 追加された行はこの色です。
  • 削除された行はこの色です。
---
category: swing
folder: NumberFormatter
title: JSpinnerで無効な値の入力を許可しない
tags: [JSpinner, JFormattedTextField, SpinnerNumberModel, DocumentListener, NumberFormatter]
author: aterai
pubdate: 2010-03-08T15:39:14+09:00
description: JSpinnerからJFormattedTextFieldを取得し、これに無効な値の入力を許可しないように設定します。
image: https://lh5.googleusercontent.com/_9Z4BYR88imo/TQTQg6Td8tI/AAAAAAAAAfs/u5mXLfk3k64/s800/NumberFormatter.png
---
* 概要 [#summary]
`JSpinner`から`JFormattedTextField`を取得し、これに無効な値の入力を許可しないように設定します。

#download(https://lh5.googleusercontent.com/_9Z4BYR88imo/TQTQg6Td8tI/AAAAAAAAAfs/u5mXLfk3k64/s800/NumberFormatter.png)

* サンプルコード [#sourcecode]
#code(link){{
JSpinner.NumberEditor editor = (JSpinner.NumberEditor) spinner.getEditor();
JFormattedTextField.AbstractFormatter formatter = editor.getTextField().getFormatter();
JSpinner spinner = new JSpinner(makeSpinnerNumberModel());
JSpinner.DefaultEditor editor =
    (JSpinner.DefaultEditor) spinner.getEditor();
JFormattedTextField.AbstractFormatter formatter =
    editor.getTextField().getFormatter();
if (formatter instanceof DefaultFormatter) {
  ((DefaultFormatter) formatter).setAllowsInvalid(false);
}
}}

* 解説 [#explanation]
上記のサンプルでは、`DefaultFormatter#setAllowsInvalid(false)`などを設定した`DefaultFormatterFactory`を作成して、`JSpinner`から取得した`JFormattedTextField`に`setFormatterFactory(...)`メソッドで追加しています。

- 上
-- `SpinnerNumberModel`を設定した通常の`JSpinner`
-- 別コンポーネントにフォーカスが移動するときに値が有効か無効かを判断
- 中
-- `SpinnerNumberModel`を設定し数値以外の無効な文字入力ができないようにした`JSpinner`
- 下
-- `SpinnerNumberModel`を設定した通常の`JSpinner`
-- 別コンポーネントにフォーカスが移動するときに値が有効か無効かを判断
-- 無効な値の場合は背景色を変更

#code{{
private static JSpinner makeSpinner2(SpinnerNumberModel m) {
  JSpinner s = new JSpinner(m);
  JSpinner.NumberEditor editor = (JSpinner.NumberEditor) s.getEditor();
  final JFormattedTextField ftf = (JFormattedTextField) editor.getTextField();
  JFormattedTextField ftf = (JFormattedTextField) editor.getTextField();
  ftf.setFormatterFactory(makeFFactory(m));
  ftf.getDocument().addDocumentListener(new DocumentListener() {
    private final Color color = new Color(255, 200, 200);
    @Override public void changedUpdate(DocumentEvent e) {
      updateEditValid();
    }

    @Override public void insertUpdate(DocumentEvent e) {
      updateEditValid();
    }

    @Override public void removeUpdate(DocumentEvent e) {
      updateEditValid();
    }

    private void updateEditValid() {
      EventQueue.invokeLater(new Runnable() {
        @Override public void run() {
          ftf.setBackground(ftf.isEditValid() ? Color.WHITE : color);
        }
      });
    }
  });
  return s;
}

private static DefaultFormatterFactory makeFFactory(final SpinnerNumberModel m) {
private static DefaultFormatterFactory makeFFactory(SpinnerNumberModel m) {
  NumberFormat format = new DecimalFormat("####0");
  NumberFormatter displayFormatter = new NumberFormatter(format);
  NumberFormatter editFormatter = new NumberFormatter(format) {
    @Override public Object stringToValue(String text) throws ParseException {
    @Override public Object stringToValue(String text)
        throws ParseException {
      try {
        Long.parseLong(text);
      } catch (NumberFormatException e) {
        throw new ParseException("xxx", 0);
        throw (ParseException) new ParseException(
            ex.getMessage(), 0).initCause(ex);
      }
      Object o = format.parse(text);
      if (o instanceof Long) {
        Long val = (Long) format.parse(text);
        Long max = (Long) m.getMaximum();
        Long min = (Long) m.getMinimum();
        if (max.compareTo(val) < 0 || min.compareTo(val) > 0) {
          throw new ParseException("xxx", 0);
          throw new ParseException("out of bounds", 0);
        }
        return val;
      }
      throw new ParseException("xxx", 0);
      throw new ParseException("not Long", 0);
    }
  };
  // editFormatter.setAllowsInvalid(false);
  // editFormatter.setCommitsOnValidEdit(true);
  editFormatter.setValueClass(Long.class);
  return new DefaultFormatterFactory(displayFormatter, displayFormatter, editFormatter);
  return new DefaultFormatterFactory(
      displayFormatter, displayFormatter, editFormatter);
}
}}

- プリミティブ型`long`を使用して`new SpinnerNumberModel(10L, 0L, 99_999L, 1L)`のように`SpinnerNumberModel`を作成すると`ClassCastException`が発生するようになった?

 java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.Long
 (java.lang.Double and java.lang.Long are in module java.base of loader 'bootstrap')
 at example.MainPanel$2.stringToValue(MainPanel.java:80)
 at java.desktop/javax.swing.JFormattedTextField.commitEdit(JFormattedTextField.java:563)

- `Long`型のオブジェクトを使用するよう修正

#code{{
private static SpinnerNumberModel makeSpinnerNumberModel() {
  Long value = 10L;
  Long minimum = 0L;
  Long maximum = 99_999L;
  Long stepSize = 1L;
  return new SpinnerNumberModel(value, minimum, maximum, stepSize);
}
}}

* 参考リンク [#reference]
- [[JTextFieldの入力を数値に制限する>Swing/NumericTextField]]
- [[SpinnerNumberModelに上限値を超える値を入力>Swing/SpinnerNumberModel]]
- [https://bugs.openjdk.org/browse/JDK-6423494 Bug ID: 6423494 SpinnerNumberModel should use getMinimum and getMaximum instead of fields]

* コメント [#comment]
#comment
- 無効な値が入力されたときの背景色の変更を`DocumentListener`で行うように修正。 -- &user(aterai); &new{2011-09-27 (火) 21:52:05};
- メモ: [https://bugs.openjdk.java.net/browse/JDK-6423494 Bug ID: 6423494 SpinnerNumberModel should use getMinimum and getMaximum instead of fields]、[[SpinnerNumberModelに上限値を超える値を入力>Swing/SpinnerNumberModel]] -- &user(aterai); &new{2011-09-27 (火) 22:12:46};

#comment