タブにボタンが - 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 はファイルのアイコンを示しています。
さて、実行してみましょう。
ここではタブに 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 メソッドを使用して、タブを削除します。
実行すると下図のように表示されます。
Tab 1 のクローズボタンをクリックすると、
ちゃんと削除することができました。
ということはタブに貼ったコンポーネントからもちゃんとイベントを受けとれるということですね。
おわりに
タブにクローズボタンが貼れるようになったのはいいのですが、なんかものたりないと思いませんか。そうです、一番右側のところにもコンポーネントを貼りたいのです。
Eclipse だとこうなっています。
この赤丸のところができればいいのですが。
JTabbedPane クラスを派生させたクラスを作って、ついでに BasicTabbedPaneUI クラスも派生させたクラスを作って、ごりごり書けばできるはずです。
すくなくとも Swing のよさは、自前ですべて描画処理をおこなっているので、ユーザが書きかえようと思えばいくらでもできるところにあります。
興味がある人はソースを解析して、やってみてはいかがですか。勉強になりますよ。
(Nov. 2005)