コンテナ(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は、「春」じゃなくて「バネ」) |
|
JFrameのコンテナのデフォルトのレイアウト。[2009-04-05]
以下のように5つのエリアに分かれており、それぞれの位置に1つずつ(1つのみ)コンポーネントを配置できる。
| 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は幅しか変わらない)
横一列あるいは縦一列に折り返し無しでコンポーネントが並べられる。(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);
}
複数のコンポーネントをサイズを考慮して整列してくれる。[2009-04-05]
ダイアログを作るときに便利。
整列したい(位置を合わせたい)コンポーネントを縦と横それぞれにグループ化して登録する。
だが、その具体的な方法(構造・使い方)がいまいち謎…というか慣れないとよく分からない。
単純なものなら以下のプログラムのように定式化してしまうのがいいかもしれない。
(→定型レイアウトを作成する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); } }
複数のコンポーネントを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()が呼ばれる。