モデルは中身で (2) [独自Modelの実装: CheckBoxListModel]
2002-01-26
前回は、チェックボックス付きリストを実現するにあたり、JListの実装方式を見てみました。今回は、実装に入っていきたいと思います。MorePasteでは、以下のクラスを作成しています。
- CheckBoxListModel
- CheckBoxCellRenderer
- ListChecker
さっそく、話の中心であるModelのクラス、CheckBoxListModelについて見ていきましょう。前回、CheckBoxListModelでは、チェックボックスの選択状態とリスト項目の有効無効の状態を保持する、と説明しました。この2つの状態について以下では、「チェック状態」、「有効状態」と表記します。
■中身はどんな
実装の中身を見てみることにしましょう。CheckBoxListのソースは、こちらです。ソースコードにはコメントが入っていません。あしからず。
クラス定義の冒頭で、フィールド及び、コンストラクタを宣言している部分です。Vectorで宣言されている、selectedとenabledが、それぞれチェック状態と有効状態を保持します。defaultSelected、defaultEnabledは、デフォルト値を格納します。引数のないコンストラクタでは、defaultSelected、defaultEnabledはそれぞれ、false、trueとなるようにしています。
…
public class CheckBoxListModel extends DefaultListModel {
private Vector selected, enabled;
private boolean defaultSelected;
private boolean defaultEnabled;
public CheckBoxListModel() {
super();
selected = new Vector();
enabled = new Vector();
defaultSelected = false;
defaultEnabled = true;
}
…
|
| CheckBoxListModelクラス |
クラス宣言及び、コンストラクタ |
指定したインデックスの場所にリストの要素を挿入するメソッド、addでは、自分自身に要素を挿入する前に、チェック状態と有効状態のデフォルト値をjava.lang.Booleanでラップして格納しています。他のスレッドの割り込みでインデックスがずれたりしないように、synchronized宣言をしています。add(
int, Object )は互換性を確保するために実装していて、defaultSelectedを用いて、add(
int, Object, boolean )を呼んでいます。
…
public synchronized void add( int
index, Object obj, boolean selected ) {
this.selected.add(
index, new Boolean( selected
) );
this.enabled.add(
index, new Boolean( defaultEnabled
) );
super.add(
index, obj );
}
public synchronized void add( int
index, Object obj ) {
add(
index, obj, defaultSelected
);
}
…
|
| CheckBoxListModelクラス |
addメソッド |
チェック状態を保持するselectedのgetter/setterメソッドは、次のように宣言しています。isSelectedAtメソッドでは、戻り値を返す際に、ラッパクラスBooleanのインスタンスから、booleanValueメソッドで値を取り出しています。setSelectedAtメソッドでは、対応するindexにエントリーがあるかどうか確かめてからラッパクラスに入れてセットするようにしています。
…
public boolean isSelectedAt( int
index ) {
Object obj
= selected.get( index );
if( obj
== null ) {
throw new
NoSuchElementException();
}
return (
(Boolean)obj ).booleanValue();
}
public void setSelectedAt( int index,
boolean selected ) {
if( get(
index ) == null ) {
throw new
NoSuchElementException();
}
this.selected.set(
index, new Boolean( selected )
);
}
…
|
| CheckBoxListModelクラス |
isSelectedAt, setSelectedAtメソッド |
■状態の保持から、メソッド
なぜ、このような実装になったのか、利点、欠点をもう少し、整理してみましょう。MorePasteのバージョン1.0では、以下のようにして実装を行っています。有効状態は、明確に使用する局面が定義できているわけではありませんから、仕様上は無くても問題ない機能です。
- リストの項目そのものは、DefaultListModelを継承して、そのまま格納する
- チェック状態、有効状態は、それぞれVectorに格納
- Vector内では、リスト項目と同じインデックスを持つようにする
- 状態はbooleanなので、java.lang.Booleanでラッピングして格納する
このような実装では、リストの項目の格納は、継承したDefaultListModelの機能をそのまま用いて行います。リスト項目そのものへのアクセスは、継承したメソッドで実行できるという利点があります。リストの長さやある項目がリストに含まれているか確認するメソッドも同じように、継承したものをそのまま使用できます。
チェック状態と有効状態に関連するメソッドは、新たに作成する必要があります。リスト項目の追加や削除は、同時に2つの状態に関するデフォルト値もしくは設定値を、Vectorに格納する処理を行うように実装します。また、リスト項目のチェック状態、有効状態の取得には、別途、get/setのメソッドを用意します。
メソッドは、以下のように整理できます。CheckBoxListModelでは、DefaultListModelから継承したメソッドでそのまま使用できないものは全てオーバーライドして、実装しなおしています。このため25のメソッドを宣言していますが、本来はそこまでする必要はないでしょう。使うメソッドだけを実装すれば十分です。ただ、その場合には、実装しておらず使用できないメソッドを間違って呼び出さないように、注意する必要がありますね。
| リスト項目の追加、削除 |
オーバーライドして実装する |
| 項目へのアクセス、リストの長さ、項目の有無の確認 |
継承したメソッドをそのまま使う |
| チェック状態と、有効状態のget/set |
新規にメソッドを実装する |
| コンストラクタ |
継承できないので、新規にメソッドを実装する |
なお、今回のような複数のVectorがインデックスでつながっている構成では、インデックスがずれるとデータが意味を持たなくなってしまいます。インデックスという細い糸が切れないようにするために、複数のVectorにまたがってインデックスが処理されるメソッドは、synchronized宣言をしておく必要があります。
■なにをやらねば
MVCのControllerのクラスであるJListは、Modelに対してListModelというインタフェースを通してアクセスします。ListModelでは以下のメソッドが宣言されていますから、それらは最低限、実装しないとコンパイルも出来ないことになります。
| public
int getSize() |
リストの長さを返す |
| public
Object getElementAt( int index ) |
指定したインデックスの値を返す |
| public
void addListDataListener( ListDataListener l ) |
モデルが変更された場合に、それを通知するリスナを、リスナのリストに追加する |
| public
void removeListDataListener( ListDataListener l ) |
モデルが変更された場合に、それを通知するリスナを、リスナのリストから削除する |
これらは、最低限実装すればいいメソッドであり、これだけあれば十分、ということになります。しかし、通常、Modelに格納されたデータを操作する必要がありますから、それを容易にするためのメソッドを実装します。ListModelの標準の実装である、DefaultListModelでは、30以上のメソッドが用意されています。同クラスでは、内部的にデータ保持のためにVectorクラスを利用しており、ほぼ同じメソッドを宣言して、処理を委譲するような構成になっています。
プログラムの中で、コレクションクラスなどでデータを保持する、何らかのクラスが既に作られていたとします。その中身をJListなどで選択させる必要があったとしましょう。その場合、あえてデータを取り出してDefaultListModelに格納しなおすのではなく、そのクラスにListModelインタフェースを実装してしまう、という方法が考えられます。そうすれば、そのクラスをJListのsetModelメソッドで、そのままModelとして使用できます。
今回のバージョンでは、複数のVectorをインデックスで串を通すような使いかたをしていますが、1.1以降では別のやり方で実装しています。1.1での実装については、いつかお話することにして、次回は残りのCheckBoxCellRendererクラス、ListCheckerクラスの内部を見て行くことにしましょう。

|