SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Swing再入門

Swing再入門 レンダラーとモデル

第3回 画面表示やデータ管理をカスタマイズする

独自モデルの追加

 では、この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();
        }
    }
}
追加した順にID番号が割り振られる。表示すると、追加した順番とは逆に表示される。
追加した順にID番号が割り振られる。表示すると、追加した順番とは逆に表示される。

 これを実行すると、JListに「ID = 1:[255:0:0]」という具合にID番号まで含めて表示がされるようになります。並び順を見てみると、ID番号の大きい順に並んでいることが分かるはずです。通常とは逆に、後からaddしたものがデータの一番前にあるのです。

 JListを作成しているcreateListを見ると、単にMyColoraddElementしているだけです。利用する側は何の操作もしていません。モデルのほうで、追加された項目を一番前に組み込み、ID番号を設定するという操作を行っているわけです。

階層構造を持ったJList

 レンダラーとモデルを自由に扱えるようになると、いろいろと面白いコンポーネントが作成できるようになります。一例として、擬似的に階層構造を持つJListを作成してみましょう。

 まず、ColorRendererMyColorを修正しましょう。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;
}
サブ項目をもったMyColorを表示するJList。項目を選択すると、サブ項目も含めて選択された状態となる。
サブ項目をもったMyColorを表示するJList。項目を選択すると、サブ項目も含めて選択された状態となる。

 実行すると、サブ項目を持っている場合には、そのセルの下に「→○○」という形でサブ項目が表示されます。サブ項目も含めて1つのセルとして扱われるため、クリックするとサブ項目も含めた項目全体が選択されます。ここでは表示だけ修正して、データ構造自体は特に操作していません。モデルもさらに修正すれば、擬似的なものでなく本格的な階層構造を持ったJListが作成できます。JTreeなどのコンポーネントは、そのようにしてモデルとレンダラーが拡張されて作られている(実際にはもっと複雑ですが)と考えれば、その仕組みがなんとなくイメージできるのではないでしょうか。

まとめ

 今回は、JListをベースに、レンダラーとモデルのカスタマイズについて説明をしました。Swingのコンポーネントは、「1つのコンポーネントでありながら、複数のオブジェクトが協調して1つの部品として動いている」という点がAWTと大きく異なります。

 Swingのコンポーネントを理解するということは、AWTのように「そのクラスに用意されているメソッドを覚える」というだけでは済みません。そのコンポーネントがどのような構造になっているのか、どのようなクラスが関係していてどう働いているのか、そうした「仕組み」も理解しなければいけません。そうして初めて、Swingのコンポーネントは理解できるようになるのです。

 これを期に、比較的単純なJListやJComboBoxなどをサンプルにして、どのようなオブジェクトがその中で動いているのか、いろいろと調べてみるとよいでしょう。AWTのシンプルなコンポーネントから想像できないような面白い仕組みが分かってくるはずですよ。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Swing再入門連載記事一覧

もっと読む

この記事の著者

掌田 津耶乃(ショウダ ツヤノ)

三文ライター&三流プログラマ。主にビギナーに向けたプログラミング関連の執筆を中心に活動している。※現在、入門ドキュメントサイト「libro」、カード型学習サイト「CARD.tuyano.com」を公開...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1674 2007/09/24 14:00

おすすめ

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー