独自モデルの追加
では、このDefaultListModelを継承して独自のモデルクラスを作成し、利用してみることにしましょう。ここでは、後から追加したものが前に配置されるモデルを作成してみることにします。また、MyColorを少し修正して、モデルに追加すると自動的に各MyColorにID番号が割り振られるようにしてみましょう。
package jp.codezine; import java.awt.*; import javax.swing.*; public class SampleApp extends JFrame { private static final long serialVersionUID = 1L; JLabel label; JList list; public SampleApp(){ this.setSize(new Dimension(250,250)); label = new JLabel("JList Sample"); this.add(label,BorderLayout.NORTH); list = this.createList(); this.add(new JScrollPane(list),BorderLayout.CENTER); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public JList createList(){ MyColor[] array = {new MyColor(255,255,255), new MyColor(255,0,0), new MyColor(0,255,0), new MyColor(0,0,255), new MyColor(255,255,0), new MyColor(0,255,255), new MyColor(255,0,255), new MyColor(0,0,0)}; JList list = new JList(); list.setCellRenderer(new ColorRenderer()); // 独自モデルを作成しJListに設定 MyListModel model = new MyListModel(); list.setModel(model); for(MyColor c:array) model.addElement(c); return list; } public static void main(String[] args) { new SampleApp().setVisible(true); } } class ColorRenderer implements ListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object object, int index, boolean isSelected, boolean hasFocus) { JLabel label = new JLabel(); try { Color c = (Color)object; label.setText(c.toString()); label.setOpaque(true); label.setFont(new Font("Serif",Font.BOLD,16)); label.setBackground(c); int n = c.getRed() + c.getGreen() + c.getBlue(); if (isSelected){ label.setForeground(c); } else { label.setForeground(n > 384 ? Color.BLACK : Color.WHITE); } } catch (ClassCastException e) { e.printStackTrace(); } return label; } } class MyColor extends Color { private static final long serialVersionUID = 1L; private int id = 0; // IDを保管する public MyColor(int r,int g,int b) { super(r, g, b); } public void setId(int n){ this.id = n; } public int getId(){ return this.id; } @Override public String toString() { return "ID = " + this.getId() + " [" + this.getRed() + ":" + this.getGreen() + ":" + this.getBlue() + "]"; } } class MyListModel extends DefaultListModel { private static final long serialVersionUID = 1L; private int counter = 0; @Override public void addElement(Object obj) { this.insertElementAt(obj,0); } @Override public void insertElementAt(Object obj, int index) { super.insertElementAt(obj, index); try { ((MyColor)obj).setId(++counter); } catch (ClassCastException e) { e.printStackTrace(); } } }

これを実行すると、JListに「ID = 1:[255:0:0]」という具合にID番号まで含めて表示がされるようになります。並び順を見てみると、ID番号の大きい順に並んでいることが分かるはずです。通常とは逆に、後からaddしたものがデータの一番前にあるのです。
JListを作成しているcreateListを見ると、単にMyColorをaddElementしているだけです。利用する側は何の操作もしていません。モデルのほうで、追加された項目を一番前に組み込み、ID番号を設定するという操作を行っているわけです。
階層構造を持ったJList
レンダラーとモデルを自由に扱えるようになると、いろいろと面白いコンポーネントが作成できるようになります。一例として、擬似的に階層構造を持つJListを作成してみましょう。
まず、ColorRendererとMyColorを修正しましょう。MyColorには、自身の中にサブ項目としてMyColor配列を保持できるようにしておきます。そしてColorRendererでは、MyColorの中にMyColor配列が保管されていたときにはそれらをさらに表示させるようにしてみましょう。
class ColorRenderer implements ListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object object, int index, boolean isSelected, boolean hasFocus) { Component result = null; JLabel label = new JLabel(); result = label; try { MyColor c = (MyColor)object; label.setText(c.toString()); label.setOpaque(true); label.setFont(new Font("Serif",Font.BOLD,16)); label.setBackground(c); int n = c.getRed() + c.getGreen() + c.getBlue(); if (isSelected){ label.setForeground(c); } else { label.setForeground(n > 384 ? Color.BLACK : Color.WHITE); } if (c.getSubdata() != null){ MyColor[] arr = c.getSubdata(); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(arr.length + 1,1,0,0)); panel.add(label); result = panel; ColorRenderer subrenderer = new ColorRenderer(); for(MyColor c2:arr){ JPanel subpanel = new JPanel(); subpanel.setLayout(new BorderLayout()); panel.add(subpanel); JLabel sublabel = new JLabel("→"); sublabel.setOpaque(true); sublabel.setForeground(c2.brighter()); sublabel.setBackground(c2); subpanel.add(sublabel,BorderLayout.WEST); subpanel.add( subrenderer.getListCellRendererComponent( list,c2,index,isSelected,hasFocus), BorderLayout.CENTER); } } } catch (ClassCastException e) { e.printStackTrace(); } return result; } } class MyColor extends Color { private static final long serialVersionUID = 1L; private int id = 0; private MyColor[] subdata = null; public void setId(int n){ this.id = n; } public int getId(){ return this.id; } public MyColor[] getSubdata() { return subdata; } public void setSubdata(MyColor[] subdata) { this.subdata = subdata; } public MyColor(int r,int g,int b) { super(r, g, b); } @Override public String toString() { return "[" + this.getRed() + ":" + this.getGreen() + ":" + this.getBlue() + "]"; } }
これでレンダラーとMyColorはできました。では、サブ項目を追加する形で、createListメソッドを修正して動作を確かめてみましょう。
public JList createList(){ MyColor[] array = {new MyColor(255,0,0), new MyColor(0,255,0), new MyColor(0,0,255), new MyColor(255,255,0), new MyColor(0,255,255), new MyColor(255,0,255)}; array[0].setSubdata(new MyColor[]{new MyColor(200,0,0), new MyColor(100,0,0)}); array[3].setSubdata(new MyColor[]{new MyColor(200,200,0), new MyColor(100,100,0)}); JList list = new JList(); list.setCellRenderer(new ColorRenderer()); DefaultListModel model = new DefaultListModel(); list.setModel(model); for(MyColor c:array) model.addElement(c); return list; }

実行すると、サブ項目を持っている場合には、そのセルの下に「→○○」という形でサブ項目が表示されます。サブ項目も含めて1つのセルとして扱われるため、クリックするとサブ項目も含めた項目全体が選択されます。ここでは表示だけ修正して、データ構造自体は特に操作していません。モデルもさらに修正すれば、擬似的なものでなく本格的な階層構造を持ったJListが作成できます。JTreeなどのコンポーネントは、そのようにしてモデルとレンダラーが拡張されて作られている(実際にはもっと複雑ですが)と考えれば、その仕組みがなんとなくイメージできるのではないでしょうか。
まとめ
今回は、JListをベースに、レンダラーとモデルのカスタマイズについて説明をしました。Swingのコンポーネントは、「1つのコンポーネントでありながら、複数のオブジェクトが協調して1つの部品として動いている」という点がAWTと大きく異なります。
Swingのコンポーネントを理解するということは、AWTのように「そのクラスに用意されているメソッドを覚える」というだけでは済みません。そのコンポーネントがどのような構造になっているのか、どのようなクラスが関係していてどう働いているのか、そうした「仕組み」も理解しなければいけません。そうして初めて、Swingのコンポーネントは理解できるようになるのです。
これを期に、比較的単純なJListやJComboBoxなどをサンプルにして、どのようなオブジェクトがその中で動いているのか、いろいろと調べてみるとよいでしょう。AWTのシンプルなコンポーネントから想像できないような面白い仕組みが分かってくるはずですよ。
