WisdomSoft - for your serial experiences.

6.3 リスト

複数の項目を並べて表示するリストは JList クラスを用いて作成します。

6.3.1 項目選択コンポーネント

複数の項目の中から目的の物を選択する作業をユーザーに求めるにはリストコンポーネントを用います。リストは設定されている項目を列挙し、そのいずれか、または複数を選択することができるコンポーネントです。通常は項目を文字列をして表示し、その中から適切なものを選択してもらうために使いますが、優れた Swing の設計を最大限に活用すれば文字列以外で項目を列挙する方法も存在します。つまり、リストは自由にカスタマイズすることもできるのです。

リストコンポーネントは javax.swing.JList クラスで実装されています。

javax.swing.JList クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--javax.swing.JComponent
                    |
                    +--javax.swing.JList
public class JList extends JComponent implements Scrollable, Accessible

JList は Object 型オブジェクトの配列を内部で管理し、これをリストとして表示します。すばらしいことに、開発者の拡張しだいであらゆるデータ型をリストとして列挙することができるということになります。

表1 JList クラスのコンストラクタとメソッド(抜粋)
コンストラクタ 解説
public JList() 空のモデルでりすとを構築する。
public JList(Vector listData) 指定された Vector の要素を表示するリストを構築する。
public JList(Object[] listData) 指定された配列の要素を表示するリストを構築する。
メソッド
public void addListSelectionListener(ListSelectionListener listener) 選択が変更されるたびに通知されるリスナを追加する。
public void removeListSelectionListener(ListSelectionListener listener) 選択が変更されるたびに通知されるリスナを削除する。
public ListSelectionListener[] getListSelectionListeners() このリストに設定されるすべての ListSelectionListener を返す。
public void setSelectedIndex(int index) 指定したインデックスのセルを選択する。
public void setSelectedIndices(int[] indices) インデックスを格納した配列で複数のセルを選択する。
public int[] getSelectedIndices() 選択されているすべてのインデックスの昇順配列を返す。
public void setSelectedValue(Object anObject, boolean shouldScroll) 指定されたオブジェクトをリストから選択する。
public Object getSelectedValue() 最初に選択されたインデックスを返す。
public Object[] getSelectedValues() 選択されたセルの値の配列を返す。
public void setSelectionInterval(int anchor, int lead) 指定された区間を選択する。
public void addSelectionInterval(int anchor, int lead) 指定された区間を現在の選択に加える。
public void removeSelectionInterval(int index0, int index1) 指定された区間の選択を解除する。
public void clearSelection() 選択をクリアする。
public boolean isSelectionEmpty() 何も選択されていない場合は true を返す。
public boolean isSelectedIndex(int index) 指定されたインデックスが選択されている場合は true を返す。
public void setListData(Object[] listData) Object の配列から表示されるリストの項目を設定する。
public void setListData(Vector listData) Vector から表示されるリストの項目を設定する。

表1は JList クラスのコンストラクタと選択に関連する主なメソッドです。これらのメソッドを駆使すれば、リスト内の選択されている項目を取得したり、プログラムから特定の項目を選択することができるようになります。

リストに項目を追加するには、コンストラクタから配列を指定するか、または setListData() メソッドを使って項目を設定することができます。最も簡単な方法は Object 型の配列で渡す方法だと思われますが、項目が動的な配列であれば Vector を使う方法もあります。

リストの選択されている項目が変更されると、リストに設定されている javax.swing.event.ListSelectionListener クラスのオブジェクトが呼び出されます。このリスナは AWT のものではなく Swing で追加されたリスナです。このリスナオブジェクトは addListSelectionListener() メソッドからリストに追加することができます。

ListSelectionListener インタフェースでは valueChanged() メソッドが宣言されています。

Item name...
public void valueChanged(ListSelectionEvent e)

このメソッドは選択範囲に関する値が変更されたときに呼び出されると定義されています。引数として受け取る javax.swing.event.ListSelectionEvent クラスのオブジェクトは選択範囲の変更に関する情報を格納しています。

javax.swing.event.ListSelectionEvent クラス
java.lang.Object
  |
  +--java.util.EventObject
        |
        +--javax.swing.event.ListSelectionEvent
public class ListSelectionEvent extends EventObject

valueChanged() メソッドが提供する情報は、選択範囲が変更された可能性のあるインデックスの最小値と最大値だけで、具体的な情報はイベントを発生させたコンポーネントを呼び出す必要があります。

表2 ListSelectionEvent クラスのメソッド
メソッド 解説
public int getFirstIndex() 選択が変更された可能性がある最初の行のインデックスを返す。
public int getLastIndex() 選択が変更された可能性がある最後の行のインデックスを返す。
public boolean getValueIsAdjusting() 複数の変更イベントのうちの 1 つである場合に true を返す。

デフォルトでリストは複数の項目を選択することが可能なので、選択範囲はいくつかの項目にまたがる可能性があります。リストの項目は配列の要素番号と同じで 0 から数えます。

コード1
import javax.swing.*;
import javax.swing.event.*;

public class Test {
	public static void main(String args[]) {
		JList list = new JList(args);
		list.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
				JList list = (JList)e.getSource();
				Object items[] = list.getSelectedValues();
				for (int i = 0 ; i < items.length ; i++)
					System.out.print(items[i] + "\t");
				System.out.println("");
			}
		});

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(list);
		win.show();
	}
}
実行結果
コード1 実行結果

コード1はプログラムの起動時に渡された引数をリストとして列挙します。JList() コンストラクタは Object 型の配列からリストを生成することができるため、main() メソッドの引数をそのまま渡すだけで項目を設定することができています。

項目が選択されたときのイベントは addListSelectionListener() メソッドで追加した ListSelectionListener オブジェクトが呼び出されることで処理することができます。このリスナが呼び出されると、プログラムは ListSelectionEvent オブジェクトからイベントを発生されたオブジェクトを取得しています。コード 06_03_00 では ListSelectionListener を呼び出すのは JList 以外に存在しないため、JList 型に源泉オブジェクトをキャストし、選択されているオブジェクトを getSelectedValues() メソッドから取得して、これを標準出力に出力します。

6.3.2 リストモデル

JList クラスのリストコンポーネントは、内部のデータを管理する方法と、選択範囲を管理する方法をモデルと呼ばれるインタフェースに委譲することで抽象化しています。これは、項目の管理方法や選択範囲の管理方法を拡張したり、置き換えたりすることができることを意味しています。

JList クラスの内部では、データを javax.swing.ListModel インタフェースのオブジェクトによって管理しています。このインタフェースはリストの長さと指定したインデックスのオブジェクトを返すことに集中しています。表3はこのインタフェースが宣言しているメソッドです。

表3 ListModel インタフェースのメソッド
メソッド 解説
public void addListDataListener(ListDataListener l) データモデルが変更されると通知される ListDataListener を追加する。
public void removeListDataListener(ListDataListener l) データモデルが変更されると通知される ListDataListener を削除する。
public int getSize() リストの長さを返す。
public Object getElementAt(int index) 指定されたインデックスで示される値を返す。

JList クラスは ListModel インタフェースを実装するオブジェクトを渡すことができます。より具体的にリストの項目を制御したければ、Object の配列ではなく ListModel を制御するべきでしょう。ListModel オブジェクトをリストに設定するには setModel() メソッド または JList() コンストラクタを使います。リストが現在保有しているデータは getModel() メソッドから取得することができます。

JList クラスのコンストラクタ
public JList(ListModel dataModel)
JList クラス setModel() メソッド
public void setModel(ListModel model)
JList クラス getModel() メソッド
public ListModel getModel()

ListModel インタフェースは javax.swing.AbstractListModel 抽象クラスで基本的な機能が実装され javax.swing.DefaultListModel で実装されています。独自の実装による格調が目的でなければ DefaultListModel クラスを使ってリストのデータをより詳細に制御することができるでしょう。本書ではこれらの実装については触れませんが、興味があるのであれば API Document を参照してください。

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

public class Test implements ListModel {
	public static void main(String args[]) {
		JList list = new JList(new Test(10));

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(list);
		win.show();
	}

	private int length = 0;
	public Test(int length) { this.length = length; }

	public void addListDataListener(ListDataListener l) {}
	public void removeListDataListener(ListDataListener l) {}
	public int getSize() { return length; }
	public Object getElementAt(int index) { return "Item" + index; }
}

コード2は極端な例ですが単純に ListModel を実装したプログラムです。コンストラクタで指定した数だけ文字列の項目を生成するモデルですが、実際に文字列の配列を内部に持つわけではなく、項目が要求されたときに文字列を生成して返しています。このように、インタフェースを実装すれば独自の内部仕様でリストのデータを表現することができるのです。この実装ではデータを変更することは無いので ListDataListener は扱いません。

また、項目の選択を管理するにはリスト選択モデルと呼ばれる javax.swing.ListSelectionModel インタフェースを実装します。このインタフェースは選択可能な項目を持つコンポーネントの選択状態を提供するためのインタフェースです。メソッドも多く宣言されているため実装するには多少煩雑なコードを書かなければなりません。デフォルトでは javax.swing.DefaultListSelectionModel クラスで実装されています。

JList に ListSelectionModel オブジェクトを設定するには setSelectionModel() メソッドを、現在のリスト選択モデルを取得するには getSelectionModel() メソッドを使います。

JList クラス setSelectionModel() メソッド
public void setSelectionModel(ListSelectionModel selectionModel)
JList クラス getSelectionModel() メソッド
public ListSelectionModel getSelectionModel()

よほど特別な理由がない限り、リストの選択範囲を独自に管理する必要などないでしょう。

JList のデフォルトでは項目の選択に制限は何もありませんが、リストの性質によっては複数の項目が選択されることを望まない場合もあるでしょう。この選択範囲の制限は JList クラスの setSelectionMode() メソッドで設定することができます。現在がどのような選択方法かは getSelectionMode() メソッドから取得することができます

JList クラス setSelectionMode() メソッド
public void setSelectionMode(int selectionMode)
JList クラス setSelectionModel() メソッド
public int setSelectionModel()

これらのメソッドが扱う数値は ListSelectionModel インタフェースのフィールドで公開されている数値定数です。これに ListSelectionModel.SINGLE_SELECTION を指定すると常に単一の項目しか選択できなくなるでしょう。ListSelectionModel.SINGLE_INTERVAL_SELECTION を指定すると、複数の項目を選択することは可能ですが、選択する綱目に間を空けることができなくなります。すなわち、必ず連続して項目を選択しなければならないのです。そして、ListSelectionModel.MULTIPLE_INTERVAL_SELECTION がデフォルトで設定されている選択モードです。この選択モードを指定すると、項目の選択に制限がなくなります。

6.3.3 セルレンダラ

JList コンポーネントが表示する選択可能な各項目をセルと呼びます。驚くべきことに、Swing ではリストのセルも自由に拡張することができるため、リストのデータモデルに合わせて項目を描画することができるのです。デフォルトではオブジェクトの toString() メソッドが返す文字列を項目に表示していましたが、例えばイメージの項目を列挙するリストも独自の拡張によって実現することができます。

JList のセルの拡張は驚くほど簡単です。JList コンポーネントの項目を描画するのは javax.swing.ListCellRenderer インタフェースを実装するオブジェクトです。このインタフェースはセルレンダラと呼ばれ、セルを描画する getListCellRendererComponent() メソッドだけを宣言しています。このメソッドは与えられた情報を基にセルとなるコンポーネントを返します。リストはセルを描画するときに、項目ごとにこのメソッドを呼び出します。

ListCellRenderer インタフェース getListCellRendererComponent() メソッド
public Component getListCellRendererComponent(
	JList list, Object value, int index,
	boolean isSelected, boolean cellHasFocus
)

list パラメータにはセルを描画しようとしているリスト、value パラメータには描画しようとしているセルの項目となるオブジェクト、index パラメータには項目のインデックスを指定します。

isSelected パラメータが true であればセルが選択されていることを表し、cellHasFocus パラメータが true であればセルにフォーカスがあることを表します。これらの情報をどのように利用するか、あるいは利用しないかは実装に委ねられます。

開発者は与えられた情報を基にセルを描画するコンポーネントを返さなければなりません。JList クラスはこのメソッドが返したコンポーネントを追加するのではなく、コンポーネントが公開している paint() メソッドを利用してセルを描画します。そのため、Swing を含むすべてのコンポーネントをセルの描画に再利用することができます。

ListCellRenderer インタフェースのオブジェクトをリストに設定するには JList クラスの setCellRenderer() メソッドを使い、現在設定されている ListCellRenderer は getCellRenderer() メソッドから取得することができます。

JList クラス setCellRenderer() メソッド
public void setCellRenderer(ListCellRenderer cellRenderer)
JList クラス getCellRenderer() メソッド
public ListCellRenderer getCellRenderer()

JList のデフォルトのセルレンダラには javax.swing.DefaultListCellRenderer クラスが設定されています。 このクラスは Swing がデフォルトで ListCellRenderer を実装するクラスです。

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

public class Test implements ListCellRenderer  {
	public static void main(String args[]) {
		JList list = new JList(args);
		list.setCellRenderer(new Test());

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(list);
		win.show();
	}

	public Component getListCellRendererComponent(
			JList list, Object value, int index, 
			boolean isSelected, boolean cellHasFocus
	) {
		JLabel label = new JLabel(value.toString());
		if (isSelected) {
			label.setForeground(Color.RED);
			label.setIcon(new ImageIcon("icon2.jpg"));
		}
		else label.setIcon(new ImageIcon("icon1.jpg"));
		return label;
	}
}
実行結果
コード3 実行結果

コード3コード1と同様にプログラムの起動時に受け取った引数からリストの項目を生成します。ただし、今回の JList はデフォルトの項目ではなく、独自のセルレンダラを使って項目を描画しています。このプログラムの項目にはアイコンが付いていて、項目が選択されると文字色とアイコンが変化します。

Test クラスは ListCellRenderer インタフェースを実装し、getListCellRendererComponent() で JLabel クラスを使ったラベルコンポーネントを生成して返します。つまり、JList が表示している各々の項目の実体は JLabel コンポーネントなのです。ただし、コンポーネントが JList に追加されているわけではなく、単純にコンポーネントが提供する paint() メソッドを使って JList が項目を描画しているにすぎません。

リストのデータモデルと共にセルレンダラを拡張すれば、事実上あらゆるオブジェクトをリストに対応させることができるようになります。