WisdomSoft - for your serial experiences.

5.4 コンテンツペイン

Swing では、コンポーネントを格納するコンテナに、ウィンドウ自身ではなくルートペインと呼ばれるコンテナを用います。ルートペインには、いくつかのコンテナが含まれていますが、中でも重要なのはコンテンツペイント呼ばれるコンテナです。

5.4.1 子コンポーネントを配置する

JApplet、JWindow、JFrame、JDialog などのトップのコンテナは、子コンポーネントを管理するコンテンツペインと呼ばれるコンテナ保有しています。

コンテンツペインとは、Swing の軽量コンテナのひとつです。コンテンツペインは透明な軽量コンテナなので子コンポーネントを含ませることができます。Swing アーキテクチャでは、重量コンポーネントであるアプレットやウィンドウは、作業領域全体にコンテンツペインを子コンポーネントととして保有し、その他の子コンポーネントは全てコンテンツペインで集中管理されるという仕組みなのです。このような Swing アーキテクチャを採用しているオブジェクトは javax.swing.RootPaneContainer インタフェースを実装しなければなりません。

表1 javax.swing.RootPaneContainer インタフェースのメソッド
メソッド 解説
Container getContentPane() コンテンツペインを返す。
Component getGlassPane() ガラスペインを返す。
JLayeredPane getLayeredPane() レイヤードペインを返す。
JRootPane getRootPane() このコンポーネントの単一の子 JRootPane を返します。
void setContentPane(Container contentPane) コンテンツペインを設定する。
void setGlassPane(Component glassPane) ガラスペインを設定する。
void setLayeredPane(JLayeredPane layeredPane) レイヤードペインを設定する。

JApplet や JFrame などの Swing を採用しているトップレベルウィンドウは、RootPaneContainer インタフェースを実装しています。このインタフェースは、先ほど説明したコンテンツペインと、ガラスペイン、レイヤードペインと呼ばれるコンポーネントを管理するコンテナであることを証明します。レイヤードペインはコンポーネントの Z 軸の描画順を管理するコンポーネントのことで、複数のコンポーネントがぶつかり合ったときに描画する順番を決定します。ガラスペインは、全てのコンポーネントの前面に張られている透明なコンポーネントで、これを独自に実装することで作業領域全体に対する演出などが容易になります。各コンポーネントの具体的な使い方は後述します。

Swing のアプレットとアプリケーションでは、従来の AWT コンテナのように直接、アプレットやアプリケーションウィンドウの子コンポーネントに指定することはできません。Swing アーキテクチャの下では、全ての Swing コンポーネントはコンテンツペインに含まれるのです。

例えば、Applet クラスでは次のように直接 Container クラスの add() メソッドからコンポーネントを追加していました。

applet.add(child);

一方、Swing アーキテクチャを採用する JApplet を使った場合には、アプレットをコンテナとすることは許されず、代わりにアプレットが保有するコンテンツペインを getContentPane() メソッドから取得して、このコンテナの add() メソッドに追加する必要があります。

applet.getContentPane().add(child);

因みに、技術的には可能ですが、Swing アーキテクチャの中に AWT 重量コンポーネントを配置したり、AWT コンテナに Swing コンポーネントを指定するという行為は推奨できません。独自に Component クラスを拡張した軽量コンポーネントも、Swing アーキテクチャに基づく設計ではないので AWT に分類されます。

コード
import javax.swing.*;
import java.awt.*;

public class Test {
	public static void main(String args[]) {
		LabelEx label = new LabelEx("Kitty on your lap");
		label.setFont(new Font("Serif" , Font.PLAIN , 20));
		label.setForeground(Color.BLUE);

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		//コンテンツペインにコンポーネントを追加
		win.getContentPane().add(label);
		win.show();
	}
}

class LabelEx extends Component {
	private String label;
	private int margin = 5;

	public LabelEx(String label) { this.label = label; }

	public Dimension getPreferredSize() {
		FontMetrics fm = getFontMetrics(getFont());
		return new Dimension(
			fm.stringWidth(this.label) + margin * 2 ,
			fm.getHeight() + margin * 2
		);
	}
	public void paint(Graphics g) {
		if (label != null) {
			FontMetrics fm = getFontMetrics(getFont());
			g.drawString(label ,
				getWidth() / 2 -  fm.stringWidth(this.label) / 2 ,
				getHeight() / 2 + fm.getDescent()
			);
		}
		g.drawRect(0 , 0 , getWidth() -1 , getHeight() - 1);
	}
}
実行結果
コード1 実行結果

コード1は Component クラスを継承した軽量コンポーネントを作成し、これを JFrame のコンテンツペインに追加したアプリケーションです。実行結果を見ると、ウィンドウにコンポーネントに設定した文字列が正しく描画されています。

JFrame オブジェクトに直接コンポーネントを追加することはできません。もし、JFrame などのウィンドウに直接子コンポーネントを追加したり、レイアウトマネージャを操作するようなことがあれば例外が発生します。Swing アーキテクチャの下では、コンポーネントを管理するのは常にコンテンツペインでなければならないのです。

ところで、LabelEx クラスは Component クラスを拡張した独自の軽量コンポーネントですが、コンテンツペインの下では getPreferredSize() が返すサイズが反映されていません。AWT のアプレットでは、getPreferredSize() が返したサイズで調整されましたが、Swing のコンテンツペインではコンテンツペイン全体に子コンポーネントのサイズが調整されています。なぜこのような違いが発生するかというと、AWT のアプレットと Swing のコンテンツペインとでは、設定されているデフォルトのレイアウトマネージャが異なるのです。コンポーネントをどのように配置するかについては、レイアウトマネージャで解説します。