SwingのJSpinner、JSlider

2021-07-22

こんにちは。

今回は、SwingでJSpinner、JSliderを扱ってみました。

JSpinner、JSliderは連続した値から一つの値をユーザに選択してもらうときに使います。JSpinnerでは値を変更するための上下ボタンが表示されます。JSliderではスライダーが表示されます。スライダーのつまみをスライドすることでユーザーは値を選択できます。

以下のプログラムを雛形として使います。

ソース
import javax.swing.JFrame;

public class Test extends JFrame {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(350, 250);

        init();

        setVisible(true);

    }

    private void init() {
        // 設置処理
    }

}

JSpinner

JSpinnerとSpinnerModel

JSpinnerでは以下のコンストラクタが用意されています。

  • JSpinner()
  • JSpinner(SpinnerModel model)

2つ目のコンストラクタでは引数がSpinnerModelインターフェースの実装クラスです。このインターフェースが値の範囲、初期値を管理しているようです。

1つ目のコンストラクタでは引数がありませんが、内部的にはSpinnerModelインターフェースの実装クラスであるSpinnerNumberModelのインスタンスが、初期値が0で最小値または最大値の制限なしで作成されます。2つ目のコンストラクタでは引数にSpinnerNumberModelクラスのインスタンスを指定するのが一般的のようです。

いくつかコンストラクタがあるのですが、ここでは以下を使ってみます。

  • SpinnerNumberModel(int value, int minimum, int maximum, int stepSize)

実際に、以下のように使ってみました。

ソース
import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;

public class Test2 extends JFrame {

    public static void main(String[] args) {
        new Test2();
    }

    public Test2() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(350, 250);

        init();

        setVisible(true);

    }

    private void init() {
        setLayout(new FlowLayout());

        JSpinner spinner1 = new JSpinner();
        add(spinner1);

        JSpinner spinner2 = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
        add(spinner2);

        JSpinner spinner3 = new JSpinner(new SpinnerNumberModel(0, -10000, 10, 1));
        add(spinner3);
    }

}
JSpinner001.png

上記では、レイアウトマネージャにFlowLayoutを設置しています。

各JSpinnerの横幅は、SpinnerNumberModelで設定した最小値と最大値から決定されるようです。spinner1は値の制限はないのですが、表示される桁数が一桁になってしまってます。なのでSpinnerNumberModelのインスタンスを使って明示的に最小値・最大値を決めた方が良いかもしれません。ただし、レイアウトマネージャにBorderLayoutを設定した場合は自動的に横幅を広げてくれます。

ChangeListener

addChangeListenerメソッドでJSpinnerにリスナーを追加することができます。引数はChangeListenerインターフェースの実装クラスです。このインターフェースはstateChangedメソッドのみ持ちます。JSpinnerの値が変更されるたびに、このメソッドが呼び出されます。

以下、実装例です。

ソース
import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;

public class Test3 extends JFrame {

    public static void main(String[] args) {
        new Test3();
    }

    public Test3() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(350, 250);

        init();

        setVisible(true);

    }

    private void init() {
        setLayout(new FlowLayout());

        JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
        add(spinner);

        spinner.addChangeListener(event -> {
            System.out.println("current value: " + spinner.getValue());
        });
    }

}

ウィンドウが表示された後、上ボタンがクリックされると、コンソールに「current value: 1」と表示されます。

JSlider

コンストラクタとBoundedRangeModel

JSliderでは内部的にはBoundedRangeModelインターフェースのが値の範囲を扱っているようです。コンストラクタJSlider(BoundedRangeModel brm)があります。他にもBoundedRangeModelを意識することなく使えるコンストラクタが用意されてます。

今回は、以下を使ってみました。

  • JSlider()
  • JSlider(int min, int max, int value)

これ以外にもコンストラクタが用意されてますが、今回は使いませんでした。また、使用されるBoundedRangeModelインターフェースの実装クラスはDefaultBoundedRangeModelのようです。

以下、3つのスライダーを設置してみました。

ソース
import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JSlider;

public class Test4 extends JFrame {

    public static void main(String[] args) {
        new Test4();
    }

    public Test4() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(350, 250);

        init();

        setVisible(true);

    }

    private void init() {
        setLayout(new FlowLayout());

        JSlider slider1 = new JSlider();
        add(slider1);

        JSlider slider2 = new JSlider(0, 10, 2);
        add(slider2);

        JSlider slider3 = new JSlider(-1000, 0, -100);
        add(slider3);
    }
}
JSlider001.png

JSlider()の場合、範囲が0から100、初期値が50の水平スライダが作成されます。

JSlider(int min, int max, int value)ではそれぞれの引数で最小値、最大値、および初期値が設定され、水平スライダが作成されます。指定できる数値については、負の数も扱えます。

数値の組み合わせに矛盾があると、IllegalArgumentExceptionが発生します。以下のエラーメッセージが出力されます。

  • java.lang.IllegalArgumentException: invalid range properties

例えば、「new JSlider(0, 10, -2)」という引数の組み合わせの場合、上記のエラーが発生します。

addChangeListenerとJLabel

スライダーの値を表示する方法については、例えばJLabelを使う方法があります。ラベルにスライダーの値を設定する方法です。スライダーの値が変更されたときのリスナーはaddChangeListenerで設定することができます。

以下では、スライダーの値が変更されたときに、JLabelに値を出力しています。

ソース
import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;

public class Test5 extends JFrame {

    public static void main(String[] args) {
        new Test5();
    }

    public Test5() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(350, 250);

        init();

        setVisible(true);

    }

    private void init() {
        setLayout(new FlowLayout());

        JSlider slider = new JSlider();
        add(slider);

        JLabel label = new JLabel(String.valueOf(slider.getValue()));
        add(label);

        slider.addChangeListener(event -> {
            label.setText(String.valueOf(slider.getValue()));
        });
    }

}
JSlider002.png

コンストラクタJSlider()を使ってインスタンスを生成しているため、初期値は50です。ラベルには50が表示されます。マウスでスライダーのつまみをドラッグして動かすと、ラベルの表示が変わります。

目盛りとラベル

スライダーに目盛りを追加することができます。以下のメソッドを使います。

  • void setPaintTicks(boolean b)
  • void setMinorTickSpacing(int n)
  • void setMajorTickSpacing(int n)

setPaintTicksの引数をtrueにして、setMinorTickSpacing、setMajorTickSpacingで小目盛、大目盛の設定をします。setPaintTicks(true)を実行しただけでは目盛りは表示されません。

更にラベルを追加したい場合、setPaintLabels(true)を実行します。初期設定ではラベルは大目盛に合わせて表示されます。ラベルを指定する場合、void setLabelTable(Dictionary labels)メソッドを使います。Dictionaryクラスは抽象メソッドを含むため、実際にはそのサブクラスであるHashtableを使います。ここで、キーと値の組み合わせはIntegerとjava.swing.JComponentです。

以下、設定例です。

ソース
import java.awt.FlowLayout;
import java.util.Dictionary;
import java.util.Hashtable;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;

public class Test6 extends JFrame {

    public static void main(String[] args) {
        new Test6();
    }

    public Test6() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(350, 250);

        init();

        setVisible(true);

    }

    private void init() {
        setLayout(new FlowLayout());

        JSlider slider1 = new JSlider();
        add(slider1);

        slider1.setPaintTicks(true);
        slider1.setMinorTickSpacing(10);
        slider1.setMajorTickSpacing(20);

        JSlider slider2 = new JSlider();
        add(slider2);

        slider2.setPaintTicks(true);
        slider2.setMinorTickSpacing(10);
        slider2.setMajorTickSpacing(20);
        slider2.setPaintLabels(true);


        JSlider slider3 = new JSlider();
        add(slider3);

        slider3.setPaintTicks(true);
        slider3.setMinorTickSpacing(10);
        slider3.setMajorTickSpacing(20);

        Dictionary<Integer, JComponent> dictionary = new Hashtable<>();
        dictionary.put(0, new JLabel("min"));
        dictionary.put(50, new JLabel("middle"));
        dictionary.put(100, new JLabel("max"));

        slider3.setPaintLabels(true);
        slider3.setLabelTable(dictionary);
    }
}
JSlider003.png

slider1では目盛りの設定をしています。小目盛10、大目盛20で設定しています。slider2ではさらにラベルを表示しています。初期設定なので、大目盛に合わせてラベルが表示されています。slider3では目盛り0、50、100に対してラベルを付けています。JComponentのサブクラスであるJLabelを使っています。ここではコンストラクタJLabel(String text)を使っていますが、JLabel(Icon image)を使えば画像がつけられるようです。

以上、参考になれば幸いです。

同じカテゴリーの前後の記事