S-JIS[2007-02-02/2009-03-29] 変更履歴

JTabbedPane(Swing)

JTabbedPaneは、 複数のペインにタブを付けて扱える。


普通に作る例

	private void initPane(Container c) {
		JEditorPane editor = new JEditorPane();
		〜

		JScrollPane scroll = new JScrollPane(editor);

		JTabbedPane tab = new JTabbedPane();
		tab.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
		tab.add(scroll);
		int n = tab.indexOfComponent(scroll);
		tab.setTitleAt(n, "タブの名札");

		c.add(tab);
	}

メソッド 概要 備考 更新日
setTabLayoutPolicy(layout) タブ数が増えたときの振る舞いを指定 SCROLL_TAB_LAYOUT スクロールする  
WRAP_TAB_LAYOUT 多段になる  
add(ペイン) タブに新しいペインを追加      
addTab(タイトル, ペイン) タブに新しいペインを追加     2009-03-21
addTab(タイトル,null,  ペイン, ツールチップ) タブに新しいペインを追加     2009-03-29
add(ペイン, n) 指定位置に新しいペインを追加      
getTabCount() タブの個数を取得     2007-02-03
indexOfComponent(ペイン) ペインの位置(番号)を取得      
getSelectedIndex() 選択されているタブの番号を取得     2007-02-03
setSelectedIndex(n) 指定番号のタブを選択     2007-02-03
setTitleAt(n, title) タブにタイトルを付ける      
getComponentAt(n) 指定位置のペインを取得      
remove(n) タブを削除     2007-02-03

タブの有無による動作変更

例えば「書式」メニューは、“タブの中にエディターが開かれていれば有効だけど、そうでなければ無効”になると格好いい。[2007-02-03]

/**
 * タブペインを拡張
 */
public class HteTabPane extends JTabbedPane {

	/**
	 * add系のメソッドは、最終的にこのメソッドが呼ばれる
	 */
	public void insertTab(String title, Icon icon, Component component, String tip, int index) {
		super.insertTab(title, icon, component, tip, index);
		if (getTabCount() > 0) {
			setEnableEditor(true);
		}
	}

	/**
	 * remove系のメソッドは、最終的にこのメソッドが呼ばれる
	 */
	public void removeTabAt(int index) {
		super.removeTabAt(index);
		if (getTabCount() <= 0) {
			setEnableEditor(false);
		}
	}

	public void setEnableEditor(boolean enable) {
		Action a = 〜;		//メニュー等のアクションのインスタンスを取得する
		a.setEnabled(enable);	//アクションの有効無効を切り替えると、メニュー表示にも自動的に反映される
	}
}

タブの並べ替え

タブの並べ替えをドラッグ&ドロップで出来るようなクラスを拡張してみた。[2007-02-03]
ただしタブが縦に並んでるとか、タブ数が増えたときのことは考慮してません(爆)(でもそんなに変な動作にはならないと思うけど…)

タブペインの“タブの並び”の行はTabbedPaneUIクラス(の派生)クラスで管理されている。
そして、タブをクリックした際のイベントは そのクラスで処理されている。
したがって、マウスのイベント処理をそこに追加した。

/**
 * タブペインを拡張
 */
public class HteTabPane extends JTabbedPane {

	public void updateUI() {
		setUI(new HteTabPaneUI());
	}
}
class HteTabPaneUI extends WindowsTabbedPaneUI {

	protected MouseListener createMouseListener() {
		return new HteTabUIMouseHandler();
	}

	//MouseHandlerはBasicTabbedPaneUIの中で定義されているクラス
	class HteTabUIMouseHandler extends MouseHandler {

		protected int drag = -1;

		public void mouseExited(MouseEvent e) {
			super.mouseExited(e);
			endDrag();
		}

		public void mousePressed(MouseEvent e) {
			super.mousePressed(e);
			drag = tabPane.getSelectedIndex();
			tabPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		}

		public void mouseReleased(MouseEvent e) {
			super.mouseReleased(e);

			if (drag >= 0) {
				int x = e.getX();
				int y = e.getY();
				int tabCount = tabPane.getTabCount();
				for (int i = 0; i < tabCount; i++) {
					// マウスの位置がタブの中にあるか
					if (rects[i].contains(x, y)) {
						move(drag, i);	endDrag();
						return;
					}
				}

				// マウスの位置が一番末尾のタブより右か
				Rectangle l = rects[tabCount - 1];
				if (l.x + l.width <= x && l.y <= y && y <= l.y + l.height) {
					move(drag, tabCount - 1);	endDrag();
					return;
				}
			}
			endDrag();
		}

		public void move(int fr, int to) {
			if (fr != to) {
				Component c = tabPane.getComponentAt(fr);
				String title = tabPane.getTitleAt(fr);
				Icon icon = tabPane.getIconAt(fr);
				String tip = tabPane.getToolTipTextAt(fr);
				// remove&insertに依らない移動方法があれば効率いいんだけど…
				tabPane.removeTabAt(fr);
				tabPane.insertTab(title, icon, c, tip, to);
				tabPane.setSelectedIndex(to);
			}
		}

		public void endDrag() {
			drag = -1;
			tabPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		}
	}
}

WindowsTabbedPaneUIというのはBasicTabbedPaneUIの派生クラスだけど、もしかするとWindows専用かも。
上位クラスで定義されているtabPaneという変数が、元となるタブペインのインスタンスを保持している。
rectsは、各タブの座標と大きさを保持している配列。しかしRectangleって、どうしてdouble型を返すメソッドしか無いんだろう…不便な。


スクロール型のタブペインでツールヒントが出ない訳

ところで、JDK1.4のスクロールタイプのタブペインは、タブにマウスカーソルを合わせてもツールヒント(ツールチップ)が出ない。[2007-02-03]
(ラップタイプのタブペインではちゃんと出る)

これは、ツールヒントの処理(マウスのイベントの処理)はタブペイン本体が行っているのに対し、
スクロールタイプのときはスクロール用のコンポーネントでイベントが処理されてしまう為、タブペインまでイベントが来ないから。

そのスクロール用のコンポーネントはprivate変数(BasicTabbedPaneUI#tabScroller)なので、手の出しようが無い…。

※JDK1.5ではスクロールタイプのタブペインでもツールヒントはちゃんと出る。


タブの初表示時にデータを準備する方法

タブがいくつか用意されているけれども各タブ(に割り当てられているコンポーネントの表示データ)を準備するのに時間がかかるような場合、最初にタブ一覧だけ表示しておいて、初めて表示するときにそのタブ専用のデータを準備する(タブ内のコンポーネントを初期化する)、という手法が考えられる。[2009-03-29]

Swingのコンポーネント(JComponent)はAncestorListenerというリスナーを登録できる。これはコンポーネントが表示されたり消されたりした際に呼び出される。
これをコンポーネントに登録しておけば、タブが選択された時(表示される時)のイベントを(JTabbedPaneの方は何もいじることなく)捕捉することが出来る。

import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
		JTabbedPane tab = new JTabbedPane();
		tab.addTab("初回タブ", new JLabel("最初に表示されるタブ"));
		tab.addTab("遅延テーブル", new JScrollPane(new LazyTable()));
class LazyTable extends JTable implements AncestorListener {
	private static final long serialVersionUID = 1L;

	public LazyTable() {
		// テーブルの初期化では、列だけ準備しておく
		// (データは表示される時に追加する)
		DefaultTableModel model = (DefaultTableModel) getModel();
		model.addColumn("列1");
		model.addColumn("列2");

		addAncestorListener(this);
	}

	private boolean inited = false;

	public void initData() {
		if (!inited) {
			//実験の為、スリープさせてみる
			try { Thread.sleep(500); } catch (InterruptedException e) {}

			// 実際のデータを追加する
			DefaultTableModel model = (DefaultTableModel) getModel();
			for (int i = 0; i < 256; i++) {
				model.addRow(new Object[] { Integer.toString(i), Integer.toHexString(i) });
			}
			inited = true;
		}
	}

//AncestorListener
	@Override
	public void ancestorMoved(AncestorEvent event) {
	}

	@Override
	public void ancestorAdded(AncestorEvent event) {
		// 表示された時に呼ばれる

		initData();
	}

	@Override
	public void ancestorRemoved(AncestorEvent event) {
		// 表示されなくなる時に呼ばれる
	}
}

この例では、テーブル(JTable)にデータを追加するのを、表示される時に行っている。

ただし、ancestorAdded()は「表示された時=表示された直後」に呼ばれる。
つまり、まずはその時点の状態で表示され、データ準備が終わってから改めて最終状態で表示し直される。
データ準備が高速に終わるならそれでも構わないだろうが、DBアクセスしたりして遅い場合には違和感のある動作になってしまう。


それが嫌なら、タブが選択された時に呼ばれるメソッドをオーバーライドしておく方法が考えられる。

		JTabbedPane tab = new LazyTabbedPane();
		tab.addTab("初回タブ", new JLabel("最初に表示されるタブ"));
		tab.addTab("遅延テーブル", new JScrollPane(new LazyTable()));
class LazyTabbedPane extends JTabbedPane {
	private static final long serialVersionUID = 1L;

	@Override
	public void setSelectedIndex(int index) {
		Component c = super.getComponentAt(index);	//選択されるコンポーネントを取得
		if (c instanceof JScrollPane) {
			c = ((JScrollPane) c).getViewport().getView();
		}

		if (c instanceof LazyTable) {
			LazyTable lazy = (LazyTable) c;
			lazy.initData();
		}

		super.setSelectedIndex(index);
	}
}

Swing目次へ戻る / Java目次へ戻る
メールの送信先:ひしだま