じゃじゃ馬ならし

 

タブにボタンが - JTabbedPane

やっぱりクローズしたい

最近はよくタブで切り替えるアプリケーションがありますね。そうなると、Java でもタブを使いたいわけです。

Swing でタブを使うには JTabbedPane クラスを使います。

でも、もうちょっとなんです。

何がもうちょっとかというと、タブにボタンが貼れないからです。

たとえば、Eclipse ではタブでエディターを切り替えますが、そのタブにはクローズボタンがついています。SWT でできるのに、Swing でできないなんて癪じゃないですか。

でも、Java SE 6 を使えば、任意のコンポーネントをタブに貼れるのです。

 

とりあえず貼ってみる

タブにコンポーネントを貼るためのメソッドは setTabComponentAt メソッドです。第一引数がタブのインデックス、第 2 引数が貼りつけるコンポーネントです。

サンプルのソースコード TabbedPaneSample1.java

 

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.UIManager;
 
public class TabbedPaneSample1 {
    public TabbedPaneSample1() {
        JFrame frame = new JFrame("TabbedPane Sample");
 
        JTabbedPane pane = new JTabbedPane();
 
        for (int i = 0; i < 5; i++) {
            pane.addTab(null, new JLabel("Tab " + i));
            pane.setTabComponentAt(i, new JLabel("Tab " + i, 
                                                 UIManager.getIcon("FileView.fileIcon"),
                                                 JLabel.TRAILING));
        }
 
        frame.add(pane);
 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 200);
        frame.setVisible(true);
    }
 
    public static void main(String[] args) {
        new TabbedPaneSample1();
    }
}

UIManager.getIcon メソッドはデフォルトで用意されているアイコンをロードするためのメソッドで、FileView.fileIcon はファイルのアイコンを示しています。

さて、実行してみましょう。

TabbedPane

ここではタブに JLabel オブジェクトを貼っていますが、実をいうとこれは今までの JTabbedPane クラスと変わりません。

JTabbedPane#addTab(String title, Icon icon, Component component) メソッドを使用した場合、内部的に JLabel オブジェクトが生成されて、タブに表示されていたのです。

この内部的に生成されていた JLabel オブジェクトの代わりに任意のコンポーネント (とはいっても Swing のコンポーネントですが) が貼れるようになったわけですね。

でも、なんで addTab(JComponent tabComponent, Component component) みたいなメソッドを作らなかったのでしょう。今の形式だとちょっと使い勝手が悪いと思うは私だけ?

 

せっかくだからクローズ

コンポーネントが貼れるようになったらやっぱりやりたいのは、タブにクローズボタンをつけることでしょう。

問題はタブに貼ったコンポーネントからイベントが取りだせるかどうかです。

サンプルのソースコード TabbedPaneSample2.java

まずは JTabbedPane オブジェクトの生成とそこに貼る JLabel オブジェクトを作る部分です。

    public TabbedPaneSample2() {
        JFrame frame = new JFrame("TabbedPane Sample");
 
        pane = new JTabbedPane();
 
        for (int i = 0; i < 5; i++) {
            JLabel label = new JLabel("Tab " + i);
            pane.addTab(null, label);
             
            JComponent tabComp = createTabComponent("Tab " + i);
            pane.setTabComponentAt(i, tabComp);
        }
 
        frame.add(pane);
 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 200);
        frame.setVisible(true);
    }

タブに貼るコンポーネントは createTabComponent メソッドで作っています。

    private JComponent createTabComponent(String title) {
        JComponent comp = new JComponent() {};
        comp.setLayout(new BorderLayout(5, 5));
 
        JLabel label = new JLabel(title, JLabel.LEFT);
        comp.add(label, BorderLayout.CENTER);
 
        ImageIcon icon = new ImageIcon("close.gif");
        JButton button = new JButton(icon);
        int width = icon.getIconWidth();
        int height = icon.getIconHeight();
        button.setPreferredSize(new Dimension(width, height));
        comp.add(button, BorderLayout.EAST);
 
        button.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    Component tabComp = ((Component)event.getSource()).getParent();
                    int index = pane.indexOfTabComponent(tabComp);
                    pane.remove(index);
                }
            });

        return comp;
    }

JComponent オブジェクトを作成して、そこにラベルとボタンを配置しています。

ボタンはそのままだとかなり大きく表示されてしまうので、イメージの大きさと同じになるようにしました。

ボタンが押されたときに、何番目のタブか知るために JTabbedPane#indexOfTabCompopnent メソッドを使っています。このメソッドの引数はタブに貼っているコンポーネントなので、JButton オブジェクトを引数にすると例外が発生してしまいます。

そこで、JComponent#getParent メソッドを使って、タブに貼っている JComponent オブジェクトを取り出し、それを indexOfTabComponent メソッドの引数にしています。

インデックスが取得できたら、後は JTabbedPane#remove メソッドを使用して、タブを削除します。

実行すると下図のように表示されます。

TabbedPane

Tab 1 のクローズボタンをクリックすると、

TabbedPane

ちゃんと削除することができました。

ということはタブに貼ったコンポーネントからもちゃんとイベントを受けとれるということですね。

 

おわりに

タブにクローズボタンが貼れるようになったのはいいのですが、なんかものたりないと思いませんか。そうです、一番右側のところにもコンポーネントを貼りたいのです。

Eclipse だとこうなっています。

Eclipse

この赤丸のところができればいいのですが。

JTabbedPane クラスを派生させたクラスを作って、ついでに BasicTabbedPaneUI クラスも派生させたクラスを作って、ごりごり書けばできるはずです。

すくなくとも Swing のよさは、自前ですべて描画処理をおこなっているので、ユーザが書きかえようと思えばいくらでもできるところにあります。

興味がある人はソースを解析して、やってみてはいかがですか。勉強になりますよ。

 

(Nov. 2005)