S-JIS[2009-03-31/2009-11-06] 変更履歴

レイアウトマネージャー(Swing)

コンテナ(JPanel等)にコンポーネントを追加する際、その配置方法を決めるのがLayoutManager
LayoutManagerの具象クラスには色々な種類があるので、目的に応じて使い分ける。


主なレイアウトマネージャー一覧

Layout
Manager
導入 内容 コンポーネントサイズの概要 リンク
BorderLayout 1.0 上・下・左・中央・右のそれぞれに1つずつコンポーネントを配置する。
フレームのコンテナのデフォルト。
上と下のコンポーネントは左右一杯に広がる。
左と右のコンポーネントは上下一杯に広がる。
中央はその中一杯に広がる。
BoxLayout   横一列あるいは縦一列に(折り返し無しで)コンポーネントを並べる。 各コンポーネントの推奨サイズ
のうち、最大のものに合わせられる。
CardLayout 1.0 複数のコンポーネントを保持するが、そのうち1つだけを表示する。
でもこれを使うならタブペインの方がいいのでは…?
ペイン一杯に広がる。
FlowLayout 1.0 横一列にコンポーネントを並べる。
入り切らなかったら折り返して次の行に並べられる。
ただ単にボタンを並べたりしたい場合に便利。
推奨サイズ
GridLayout 1.0 m×nの升目に区切ってその中に配置する。 升目のサイズ一杯に広がる。
GridBagLayout 1.0 m×nの升目に区切ってその中に配置する。
升目同士を繋いで大きなマスにすることが出来る。
 
GroupLayout 1.6 コンポーネントを縦横にグルーピングする。
同じグループ内では同じような位置に(整列して)配置される。
ダイアログを作るのに便利。
推奨サイズ。
場合によっては最大サイズまで広がる。
OverlayLayout   コンポーネントを互いに重ね合わせられる。  
SpringLayout 1.4 コンポーネント同士の間隔を指定する。
ウィンドウサイズを変更すると、間隔指定に応じて位置が変化する。
(Springは、「春」じゃなくて「バネ」)
 

BorderLayout

JFrameのコンテナのデフォルトのレイアウト。[2009-04-05]
以下のように5つのエリアに分かれており、それぞれの位置に1つずつ(1つのみ)コンポーネントを配置できる。

BorderLayoutの位置を表す定数
NORTH
(PAGE_START)
WEST
(LINE_START)
CENTER EAST
(LINE_END)
SOUTH
(PAGE_END)
	private void initPane(Container c) {
		c.add(new JButton("NORTH"), BorderLayout.NORTH);
		c.add(new JButton("SOUTH"), BorderLayout.SOUTH);
		c.add(new JButton("WEST"), BorderLayout.WEST);
		c.add(new JButton("EAST"), BorderLayout.EAST);
		c.add(new JButton("CENTER"), BorderLayout.CENTER);
	}

NORTHとPAGE_STARTを同時に指定すると、PAGE_STARTの方が優先される。PAGE_ENDやLINE系も同様。
それ以外の指定では、同じ位置に再度add()すると、後から追加した方が有効になる。
位置を何も指定せずにadd()した場合はCENTERが指定されたことになる。

BorderLayoutのコンテナ(ペイン)のサイズを変更した場合、BorderLayout内の各コンポーネントもサイズが変わる。
その際、横方向(幅)はNORTH・CENTER・SOUTHが変わる。
縦方向(高さ)はWEST・CENTER・EASTが変わる。
コンポーネントの種類によっては、登録する位置がCENTERでもPAGE_STARTでも表示は同じように見えるが、サイズ変更した場合の動作が変わってくる。 (CENTERだと幅も高さも変わるが、PAGE_STARTは幅しか変わらない)


BoxLayout

横一列あるいは縦一列に折り返し無しでコンポーネントが並べられる。(FlowLayoutだと折り返される)
各コンポーネントは基本的に推奨サイズ、というかウィンドウ内に入り切る範囲で最大サイズになる。

例えば以下のようなレイアウトを作れる。

テキストエリア ボタン
大きな
スクロール
エリア

↓実質的には2つのJPanel(BoxLayout)を下のように使用する。

テキストエリア ボタン
大きな
スクロール
エリア

一行目(テキストエリア+ボタン)を横方向のBoxLayoutにする。
ボタンサイズは固定で、テキストエリアは無制限とする。すると、ウィンドウを横に広げた際にも、ボタンサイズは変わらずにテキストエリアだけ幅が変わる。
(無制限と言っても、実際にはウィンドウ幅より大きい値をセットしておく。現実ではウィンドウ幅は最大でもせいぜい1280程度なので、32768(Short.MAX_VALUE)とかにしておけばよい)

一行目と二行目(スクロールエリア)を縦方向のBoxLayoutにする。(一行目のBoxLayoutとは別インスタンス)
一行目の高さを固定にしておけば、ウィンドウを縦に広げた際にはスクロールエリアだけが変化する。

import javax.swing.BoxLayout;
import javax.swing.JPanel;
	private void initPane(Container c) {
		// 1行目の内部
		JComponent c1 = new JTextField("テキストエリア");
		JComponent c2 = new JButton("ボタン");

		//ボタンのサイズは固定なので、テキストエリアのサイズをそれに合わせる
		Dimension sz1 = c1.getMaximumSize();
		Dimension sz2 = c2.getMaximumSize();
		sz1.width  = Short.MAX_VALUE;	//テキストエリアの横幅はいくらでも大きく出来ることにする
		sz1.height = sz2.height;		//テキストエリアの高さはボタンの高さまでとする
		c1.setMaximumSize(sz1);

		// 横のBoxLayout(1行目)
		JPanel panel1 = new JPanel();
		panel1.setLayout(new BoxLayout(panel1, BoxLayout.LINE_AXIS));
		panel1.add(c1);	//テキストエリア
		panel1.add(c2);	//ボタン

		// スクロールエリア(2行目)
		JScrollPane scroll = new JScrollPane(new JTree());

		// 縦のBoxLayout
		JPanel panelV = new JPanel();
		panelV.setLayout(new BoxLayout(panelV, BoxLayout.PAGE_AXIS));
		panelV.add(panel1);	//1行目
		panelV.add(scroll);	//2行目

		c.add(panelV);
	}

GroupLayout

複数のコンポーネントをサイズを考慮して整列してくれる。[2009-04-05]
ダイアログを作るときに便利。

整列したい(位置を合わせたい)コンポーネントを縦と横それぞれにグループ化して登録する。
だが、その具体的な方法(構造・使い方)がいまいち謎…というか慣れないとよく分からない。


例1

単純なものなら以下のプログラムのように定式化してしまうのがいいかもしれない。
(→定型レイアウトを作成するGroupLayoutUtil [2009-11-06]

サンプルの構造イメージ
1行テキスト
ラベルのみ  
複数行テキスト
コンボボックス

※これ全体のサイズを変更した場合、左の列はラベルのみなので、サイズは不変。
 右の列の各コンポーネントのサイズが全体に合わせて伸縮する。
 1行テキスト(JTextField)やコンボボックス(JComboBox)は高さは不変なので、縦方向の高さは複数行テキスト(JTextArea)だけが変わる。
 横幅は、この例では右の列全てのコンポーネントが変わる。
 要するに、各コンポーネントは、個々のsetMaximumSize()で指定された最大値まで広がる。

import javax.swing.GroupLayout;
import javax.swing.GroupLayout.ParallelGroup;
import javax.swing.GroupLayout.SequentialGroup;
	private void initPane(Container container) {
		GroupLayout layout = new GroupLayout(container);
		container.setLayout(layout);

		// コンポーネント同士の間隔を空ける設定
		layout.setAutoCreateGaps(true);
		layout.setAutoCreateContainerGaps(true);

		// 作りたい構造
		JComponent[][] compos = {
			{ new JLabel("1行テキスト"),    new JTextField()                 },
			{ new JLabel("ラベルのみ"),     null                             },
			{ new JLabel("複数行テキスト"), new JScrollPane(new JTextArea()) },
			{ new JLabel("コンボボックス"), new JComboBox()                  },
		};

		int ny = compos.length;
		int nx = compos[0].length;

		// 水平方向のグループ
		{
			SequentialGroup hg = layout.createSequentialGroup();
			for (int x = 0; x < nx; x++) {
				ParallelGroup pg = layout.createParallelGroup();
				for (int y = 0; y < ny; y++) {
					JComponent c = compos[y][x];
					if (c != null) {
						pg.addComponent(c);
					}
				}
				hg.addGroup(pg);
			}
			layout.setHorizontalGroup(hg);
		}

		// 垂直方向のグループ
		{
			SequentialGroup vg = layout.createSequentialGroup();
			for (int y = 0; y < ny; y++) {
				ParallelGroup pg = layout.createParallelGroup(GroupLayout.Alignment.BASELINE);
				for (int x = 0; x < nx; x++) {
					JComponent c = compos[y][x];
					if (c != null) {
						pg.addComponent(c);
					}
				}
				vg.addGroup(pg);
			}
			layout.setVerticalGroup(vg);
		}
	}

例2

複数のコンポーネントを1つのコンポーネントの様に扱ってレイアウトしたい場合(下記の例の「テキストボックス」と「ボタン1-1」)、別途グループ化する。[2009-09-26]
(→GroupLayoutUtilを使って表現する方法 [2009-11-06]

サンプルの構造イメージ
1行目ラベル
2行目ラベル
	private JComponent line1Label    = new JLabel("1行目ラベル");
	private JComponent line1Text     = new JTextField("テキストボックス");
	private JComponent line1Button11 = new JButton("ボタン1-1");
	private JComponent line1Button12 = new JButton("ボタン1-2");

	private JComponent line2Label   = new JLabel("2行目ラベル");
	private JComponent line2List    = new JScrollPane(new JList(new String[] {"選択肢1", "選択肢2"}));
	private JComponent line2Button2 = new JButton("ボタン2");
	private void initPane(Container container) {
		GroupLayout layout = new GroupLayout(container);
		container.setLayout(layout);

		// コンポーネント同士の間隔を空ける設定
		layout.setAutoCreateGaps(true);
		layout.setAutoCreateContainerGaps(true);

		// 列毎(水平グループ)
		{
			SequentialGroup hg = layout.createSequentialGroup();
			{ // 1列目
				ParallelGroup pg = layout.createParallelGroup();
				pg.addComponent(line1Label);
				pg.addComponent(line2Label);
				hg.addGroup(pg);
			}
			{ // 2列目
				ParallelGroup pg = layout.createParallelGroup();
				{
					SequentialGroup g = layout.createSequentialGroup();
					g.addComponent(line1Text);
					g.addGap(0);		//間隔を詰める
					g.addComponent(line1Button11);
					pg.addGroup(g);
				}
				pg.addComponent(line2List);
				hg.addGroup(pg);
			}
			{ // 3列目
				ParallelGroup pg = layout.createParallelGroup();
				pg.addComponent(line1Button12);
				pg.addComponent(line2Button2);
				hg.addGroup(pg);
			}
			layout.setHorizontalGroup(hg);
		}

		// 行毎(垂直グループ)
		{
			SequentialGroup vg = layout.createSequentialGroup();
			{ // 1行目
				ParallelGroup pg = layout.createParallelGroup(GroupLayout.Alignment.BASELINE);
				pg.addComponent(line1Label);
				pg.addComponent(line1Text);
				pg.addComponent(line1Button11);
				pg.addComponent(line1Button12);
				vg.addGroup(pg);
			}
			{ // 2行目
				ParallelGroup pg = layout.createParallelGroup(GroupLayout.Alignment.BASELINE);
				pg.addComponent(line2Label);
				pg.addComponent(line2List);
				pg.addComponent(line2Button2);
				vg.addGroup(pg);
			}
			layout.setVerticalGroup(vg);
		}

レイアウトの動的変更

コンポーネント(パネル・ペイン・ウィンドウ(フレーム)ダイアログ)に登録されたレイアウトマネージャーは、そのコンポーネントのサイズが変わると、自動的に内部のコンポーネントの位置やサイズを計算し直す。[2009-04-05]
(そもそもその為にレイアウトマネージャーというものがあるはず)

ただ、場合によっては、ウィンドウサイズの変更時に特定のコンポーネントだけ独自にサイズを変えたいといった事もあるかもしれない。
これを実現する為には、サイズ変更のイベントを捕捉し、変更前に各コンポーネントのサイズを設定し直せばよい。

コンポーネントのサイズが変更されたことを検知するイベントは、ComponentListener#componentResized()
ただしこのイベントは、サイズが変更されたに発生する。したがって、今回の目的には使えない。

(しかしサイズ変更前に発生するイベント・リスナーが何だか分からなかったので)
コンポーネントでレイアウト変更時に呼ばれるdoLayout()をオーバーライドすれば良さそう。


レイアウトマネージャーの動作

独自のレイアウトマネージャーを作るには、LayoutManagerインターフェース(あるいはそこからさらに派生しているLayoutManager2インターフェース)を実装する。[2009-09-26]

実装すべきメソッド
メソッド名 呼ばれるタイミング 処理すべき内容
LayoutManager addLayoutComponent(名前, コンポーネント)    
LayoutManager2 addLayoutComponent(コンポーネント, 制約) コンテナにコンポーネントが追加された際に呼ばれる。 コンポーネント毎に独自の制約(位置情報とか)を持たせる場合、ここで自分の中に保持しておく。
(HashMap<コンポーネント, 制約>とか)
LayoutManager removeLayoutComponent(コンポーネント)    
LayoutManager preferredLayoutSize(コンテナ) コンテナ#getPreferredSize()が呼ばれた時。 推奨サイズを返す。
LayoutManager minimumLayoutSize(コンテナ) コンテナ#getMinimumSize()が呼ばれた時。 最小サイズを返す。
LayoutManager2 maximumLayoutSize(コンテナ)   最大サイズを返す。
new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)とか。
LayoutManager2 getLayoutAlignmentX(コンテナ)
getLayoutAlignmentY(コンテナ)
   
LayoutManager2 invalidateLayout(コンテナ) レイアウトが無効になる際に呼ばれる。 保持している位置・サイズ情報のクリア。
必要なければ無処理でよい。
LayoutManager layoutContainer(コンテナ) コンポーネントを配置(表示)する際に呼ばれる。 コンテナ内の全コンポーネントに対し、位置・サイズを設定する。
(全コンポーネント = コンテナ#getComponents())
(コンポーネント#setBounds()を呼び出して設定)
(位置決定にはコンテナ#getInsets()も考慮に入れる)

class MyLayout implements LayoutManager2 {
	〜
}
		Container c = frame.getContentPane();
		c.setLayout(new MyLayout());

		JTextField f1 = new JTextField();
		c.add(f1);	//この中で、addLayoutComponent(f1, null)が呼ばれる。
		frame.setVisible(true);	//これが実行されると、
					//invalidateLayout()が(JDK1.6の場合は2回)呼ばれ、その後に
					//layoutContainer()が呼ばれる。

フレームをドラッグして)サイズ変更(リサイズ)すると、まずinvalidateLayout()が呼ばれ、次にlayoutContainer()が呼ばれる。


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