WisdomSoft - for your serial experiences.

6.5 スピナー

整数のような連続的な値の入力には JSpinner クラスを用いたすぴなーと呼ばれる入力コンポーネントを用いると便利です。

6.5.1 連続した値の入力

リストやコンボボックスは特定の定められた、表示する時点で明確な項目を管理するコンポーネントでしたが、表示する時点では全体が不明確な、または全体を表示することに意味がない項目という概念も存在します。例えば、数列や日付の列挙などが考えられるでしょう。

数列のような連続した要素を表示したり、編集するにはスピナーと呼ばれるコンポーネントを利用します。スピナーは編集可能なコンボボックスと同様にテキスト入力コンポーネントが表示され、これにキーボードから文字や数値を入力することができます。しかし、スピナーの目的はテキストの入力ではなく、連続した要素を選択するものです。性質はリストやコンボボックスに似ているのです。

スピナーは、リストとは異なり項目の配列を管理することはありません。その代わり、スピナーには現在選択されている値と、選択されている値から相対的な次の値と前の値を管理しています。このような項目の管理方法は、まさに数列の選択に適しています。現在 10 という値が選択されていれば、前の値は 9、次の値は 11 となるでしょう。

スピナーコンポーネントを利用するには javax.swing.JSpinner クラスを使います。

javax.swing.JSpinner クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--javax.swing.JComponent
                    |
                    +--javax.swing.JSpinner

public class JSpinner extends JComponent
public class JSpinner extends JComponent

デフォルトのスピナーは、最小値や最大値を持たない 0 から始まる数列を管理しています。スピナーコンポーネントをコンテナに表示すると、端に小さな矢印ボタンが付属してます。このボタンを押すことで、次の値と前の値に移動することができるのです。スピナーは編集可能なので、直接キーボードから数値を入力することもできます。

表1 JSpinner クラスのコンストラクタと現在の値に関するメソッド(抜粋)
コンストラクタ 解説
lic JSpinner() 初期値が 0 で最小値または最大値の制限がないスピナーを構築する。
public JSpinner(SpinnerModel model) 指定したモデルでスピナーを構築する。
メソッド
public void addChangeListener(ChangeListener listener) モデルに変更が加えられると通知されるリスナを追加する。
public void removeChangeListener(ChangeListener listener) このスピナーから ChangeListener を削除する。
public ChangeListener[] getChangeListeners() 設定されているすべての ChangeListener の配列を返す。
public void setValue(Object value) モデルの現在の値を変更する。
public Object getValue() モデルの現在の値を返す。
public Object getNextValue() 現在の値に対する次のオブジェクトを返す。
public Object getPreviousValue() 現在の値に対する前のオブジェクトを返す。

JSpinner クラスのコンストラクタと主なメソッドは表 06_05_00 に表します。コンストラクタを見てわかるように、このクラスもリストと同様にコンポーネントが扱うデータを管理するデータモデルを保有しています。データモデルの詳細は後述しますが、デフォルトでは連続した値を管理するモデルが設定されています。

現在の値がエディタやプログラムによって変更されると、設定されている javax.swing.event.ChangeListener インタフェースのオブジェクトが呼び出されます。このリスナは何らかのオブジェクトの変更を受けるメソッド stateChanged() メソッドが宣言されています。スピナーの値が変更されると、このメソッドが呼び出されます。

ChangeListener インタフェース stateChanged() メソッド
public void stateChanged(ChangeEvent e)

このメソッドが受け取る引数は javax.swing.event.ChangeEvent クラスのオブジェクトです。 このイベントはリスナが対象としているオブジェクトの状態を通知するだけなので、イベント引数は新しいメソッドを宣言していません。

コード'1
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;

public class Test extends JFrame implements ChangeListener {
	public static void main(String args[]) {
		JFrame win = new Test();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.show();
	}

	private JSpinner spinner = new JSpinner();
	private JLabel label = new JLabel();
	public Test() {
		spinner.addChangeListener(this);

		getContentPane().add(spinner , BorderLayout.NORTH);
		getContentPane().add(label , BorderLayout.SOUTH);
	}

	public void stateChanged(ChangeEvent e) {
		String text = "現在の値=" + spinner.getValue() +
			",次の値=" + spinner.getNextValue() +
			",前の値=" + spinner.getPreviousValue()
		;
		label.setText(text);
	}
}
実行結果
コード1 実行結果

コード 06_05_00 はデフォルトのモデルを採用しているスピナーを表示し、このスピナーに ChangeListener を追加してモデルの変化を監視するプログラムです。スピナーコンポーネントの右側に、上と下の矢印が付いているボタンがあります。このボタンを押すと、スピナーの現在の値が次の値、または前の値に変化します。このコンポーネントは、ユーザーに数値を入力させるときなどに便利です。すばらしいことに、入力フィールドに、わざと数字以外の文字を入力しても、ChangeListener は呼び出されません。つまり、不正な値を現在の値にすることはできないのです。

JSpinner オブジェクトは、設定されている現在の値を getValue()、次の値を getNextValue()、前の値を getPreviousValue() メソッドで提供しています。このプログラムでは、変更イベントが発生したときに、スピナーから各値を取り出してラベルの表示してます。

現在の値や、次の値、前の値という概念はスピナーに設定されているモデルによって異なります。デフォルトのスピナーでは数値要素を対象としているため、次の値を 1 加算した値、前の値を 1 減算した値としています。

6.5.2 スピナーモデル

スピナーを使って、数値以外の何らかの要素を入力させたい場合は、スピナーのモデルを独自に実装します。スピナーは javax.swing.SpinnerModel インタフェースをモデルとして保有しています。このモデルは、Object 型の現在の値、次の値、前の値をそれぞれ提供します。

表2 SpinnerModel インタフェースのメソッド
メソッド 解説
public void addChangeListener(ChangeListener l) モデルのリスナーリストに ChangeListener を追加する。
public void removeChangeListener(ChangeListener l) モデルのリスナーリストから ChangeListener を削除する。
public void setValue(Object value) モデルの現在の値を変更する。
public Object getValue() 現在の値を返す。
public Object getNextValue() 現在の値に対する次のオブジェクトを返す。
public Object getPreviousValue() 現在の値に対する前のオブジェクトを返す。

JSpinner クラスが提供していた getValue() や getNextValue()、getPreviousValue() メソッドは、単純に SpinnerModel インタフェースのメソッドを呼び出して委譲しているだけです。ChangeListener の呼び出しのタイミングも同様で、モデルの現在の値が変更された場合にリスナを呼び出すと定められています。

Swing の実装では、まず SpinnerModel を簡易実装している javax.swing.AbstractSpinnerModel クラスを定義してます。このクラスは抽象クラスです。

javax.swing.AbstractSpinnerModel クラス
java.lang.Object
  |
  +--javax.swing.AbstractSpinnerModel
public abstract class AbstractSpinnerModel extends Object implements SpinnerModel

このクラスでは変更イベントを通知する手段をサブクラスに提供しています。サブクラスはリスナを管理する必要はなく、現在の値を変更した時点で AbstractSpinnerModel クラスの fireStateChanged() メソッドを呼び出すだけで良いのです。

AbstractSpinnerModel クラス fireStateChanged() メソッド
protected void fireStateChanged()

このメソッドは、設定されている変更リスナのメソッドを呼び出してくれます。現在の値を変更するタイミングは、サブクラスが自由に定めることができるのです。

JSpinner クラスがデフォルトで実装しているモデルは javax.swing.SpinnerNumberModel クラスです。このクラスは、上限と下限を持つ数値要素を提供しています。

javax.swing.SpinnerNumberModel クラス
java.lang.Object
  |
  +--javax.swing.AbstractSpinnerModel
        |
        +--javax.swing.SpinnerNumberModel
public class SpinnerNumberModel extends AbstractSpinnerModel implements Serializable

このクラスを使えば、数値を入力する専用のスピナーを実装することができます。必要であれば最大値と最小値を指定することができ、次の値と前の値の増減値も設定することができます。このクラスのコンストラクタと主なメソッドは表3を参照してください。

表3 SpinnerNumberModel クラスのコンストラクタとメソッド(抜粋)
コンストラクタ 解説
public SpinnerNumberModel() 最小及び最大値を指定せず、stepSize を 1、初期値を 0 にしてモデルを構築する。
public SpinnerNumberModel(int value, int minimum, int maximum, int stepSize)

指定された値、最小値、最大値、値の変更サイズでモデルを構築する。

public SpinnerNumberModel(double value, double minimum, double maximum, double stepSize)
public SpinnerNumberModel(Number value, Comparable minimum, Comparable maximum, Number stepSize)
メソッド
public void setMaximum(Comparable maximum) 数値の最大値を設定する。
public Comparable getMaximum() 数値の最大値を返す。
public void setMinimum(Comparable minimum) 数値の最小値を設定する。
public Comparable getMinimum() 数値の最小値を返す。
public void setStepSize(Number stepSize) 次の値と前の値で計算される値変更のサイズを設定する。
public Number getStepSize() 次の値と前の値で計算される値変更のサイズを返す。

リストやコンボボックスのように Object 型の配列を項目とするには javax.swing.SpinnerListModel クラスを使います。このクラスは単純に、コンストラクタから List インタフェースのオブジェクトか、Object 型の配列を受け取ります。これを List インタフェースとして管理し、インデックス順に現在の値と次の値、前の値を提供します。

また、日付を項目とする javax.swing.SpinnerDateModel クラスも存在します。ユーザーに何らかの日付情報を入力してもらう場合、このモデルをスピナーに設定すると良いでしょう。これらのクラスも AbstractSpinnerModel クラスを継承しています。

SpinnerModel インタフェースを実装するオブジェクトをスピナーに設定するには JSpinner クラスの setModel() メソッドを使い、現在設定されているモデルを取得するには getModel() メソッドを使います。

JSpinner クラス setModel() メソッド
public void setModel(SpinnerModel model)
JSpinner クラス getModel() メソッド
public SpinnerModel getModel()

これで、デフォルト以外の独自のモデルをスピナーに設定することができます。

コード2
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;

public class Test extends JFrame implements ChangeListener {
	public static void main(String args[]) {
		JFrame win = new Test();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.show();
	}

	private JSpinner spinner = new JSpinner();
	private JLabel label = new JLabel();
	public Test() {
		Icon icon[] = {
			new ImageIcon("icon1.jpg") ,
			new ImageIcon("icon2.jpg") ,
			new ImageIcon("icon3.jpg") ,
			new ImageIcon("icon4.jpg")
		};

		spinner.setModel(new SpinnerListModel(icon));
		spinner.addChangeListener(this);

		getContentPane().add(spinner , BorderLayout.NORTH);
		getContentPane().add(label , BorderLayout.SOUTH);
	}

	public void stateChanged(ChangeEvent e) {
		String text = "現在の値=" + spinner.getValue() +
			",次の値=" + spinner.getNextValue() +
			",前の値=" + spinner.getPreviousValue()
		;
		label.setText(text);
	}
}
実行結果
コード2 実行結果

コード2はアイコンの配列を管理する javax.swing.SpinnerListModel オブジェクトをスピナーのモデルとして設定しています。そのため、このスピナーは数字ではなく Icon オブジェクトをエディタに表示してます。当然ですが、テキストエディタでは入力によってアイコンを編集することはできません。

6.5.3 エディタコンポーネント

スピナーはデフォルトでテキスト入力コンポーネントを表示していますが、スピナーが表示するエディタコンポーネントを任意の Swing コンポーネントに変更することができます。

例えば、スピナーのデータモデルがイメージを扱っているのであれば、それを表示するコンポーネントはやはりイメージを扱えるべきだと考えられます。エディタはスピナーのモデル変更イベントを監視し、モデルの状態に合わせて表示する内容や編集を管理します。

スピナーのエディタを変更するには JSpinner クラスの setEditor() メソッドを、エディタコンポーネントを取得するには getEditor() メソッドを使います。

JSpinner クラス setEditor() メソッド
public void setEditor(JComponent editor)
JSpinner クラス getEditor() メソッド
public JComponent getEditor()

setEditor() メソッドの引数として渡したコンポーネントは、スピナーが値を表示し、値を編集するコンポーネントとして利用されます。コンポーネントの端にはスピナーが提供する矢印ボタンが付属しますが、それ以外はコンポーネントをそのまま表示します。例えば、編集のできないスピナーとしてラベルコンポーネントを設定することも可能です。

コード3
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;

public class Test extends JFrame implements ChangeListener {
	public static void main(String args[]) {
		JFrame win = new Test();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.show();
	}

	private JSpinner spinner = new JSpinner();
	private JLabel label = new JLabel();
	public Test() {
		Icon icon[] = {
			new ImageIcon("icon1.jpg") ,
			new ImageIcon("icon2.jpg") ,
			new ImageIcon("icon3.jpg") ,
			new ImageIcon("icon4.jpg")
		};
		label.setIcon(icon[0]);

		spinner.setModel(new SpinnerListModel(icon));
		spinner.addChangeListener(this);
		spinner.setEditor(label);

		getContentPane().add(spinner , BorderLayout.NORTH);
	}
	public void stateChanged(ChangeEvent e) {
		label.setIcon((Icon)spinner.getValue());
	}
}
実行結果
コード3 実行結果

コード3は、JLabel コンポーネントをスピナーの編集用コンポーネントとして設定しています。ラベルはデータを編集することはできませんが、このプログラムではモデルの変更イベントを監視し、常にモデルが選択している現在の値を表示しています。このプログラムのスピナーのモデルはアイコンを提供するため、ラベルはスピナーの現在の値を Icon 型にキャストして表示しています。