先週に引き続き,今週もコンポーネントのレイアウトに関する話題です。Java SE 6で新たに導入されたレイアウト・マネージャjavax.siwng.GroupLayoutクラスを紹介します。

GroupLayoutクラスは,もともとNetBeans 5.0で導入されたMatesseというGUIビルダーで使用されていたものです。NetBeansではGUIのデザインを直感的に行うことができ,複雑な配置でも容易に作成できます。

Matisseで導入されたGroupLayoutクラスは次のような特徴を持っています。

  1. レイアウトの自由度が高い
  2. コードの自動生成が容易

Java SE 6にGroupLayoutクラスが取り入れられたので,今後は他のGUIビルダーでもGroupLayoutクラスが採用されることが予想されます。

GroupLayoutクラスはツールで使われることが前提かもしれませんが,そこをあえて手で書いてみましょう。

GroupLayoutはコンポーネントを水平方向と垂直方向の両方からグループ化してレイアウトします。

グループの構成
図1 グループの構成

グループにはシーケンシャルなグループとパラレルなグループの2種類があります。シーケンシャルなグループは,グループの要素を順々に配置するグループです。一方のパラレルなグループは,センタリングやベースライン整列などを行います。

例えば,先週と同じようにラベル,テキスト・フィールド,ボタンが水平方向に並んだGUIを考えてみましょう。

まず水平方向です。

水平方向にはラベル,テキスト・フィールド,ボタンと順々に並べていくので,シーケンシャルなグループが一つあればOKです。

垂直方向で見ると,三つのコンポーネントは同じ位置になるので,これはパラレルな一つのグループとして扱うことができます。

言葉だけだとわかりにくいので,図で表してみます(図1)。図中の赤の実践が水平方向のシーケンシャル・グループ,青の点線が垂直方向のパラレル・グループです。しかし,図にしてもわかりにくいですね(笑)。

他の例も見るうちに,だんだんとわかるようになるので,今はこのままで進みましょう。

それでは,これを実際にサンプルで確かめてみます。

サンプルのソースコード GroupLayoutSample1.java

GroupLayoutクラスで扱うグループはGroupLayoutクラスの内部クラスGroupで表します。シーケンシャル・グループはGroupクラスの派生クラスであるSequentialGroupクラスで表します。このSequentialGroupクラスもGroupLayoutクラスの内部クラスです。

同様にパラレル・グループはParallelGroupクラスで表します。こちらもGroupLayoutクラスの内部クラスです。

SequentialGroupクラスもParallelGroupクラスも直接オブジェクトを生成することはできません。その代わりGroupLayoutクラスのcreateSequentialGroupメソッド,createParallelGroupメソッドを使用します。

グループにコンポーネントを登録する場合はGroup#addComponentメソッドを使用します。ここでは使用しませんが,グループは入れ子にできます。その場合はGroup#addGroupメソッドを使用してグループを入れ子にします。

最終的に,グループをレイアウト・マネージャに登録するときにはGroupLayout#addHorizontalGroupメソッドとaddVerticalGroupメソッドを使用します。

まずはGroupLayoutクラスのオブジェクト生成部分です。

// GroupLayout の生成
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);

// 自動的にコンポーネント間のすき間をあけるようにする
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);

GroupLayoutオブジェクトはコンストラクタの引数にコンテナを指定します。

setAutoCreateGapsメソッドは,レイアウトするコンポーネント間に自動的にすき間を入れるように設定するためのメソッドです。同様にsetAutoCreateContainerメソッドは,コンテナとコンテナのエッジに最も近いコンポーネント間にすき間を自動的に入れるためのメソッドです。

どちらもデフォルトではすき間を入れないようになっています。

次に水平方向のグループです。

// 水平方向のグループ
GroupLayout.SequentialGroup hGroup
    = layout.createSequentialGroup();

// グループにコンポーネントを追加
hGroup.addComponent(label)
      .addComponent(field)
      .addComponent(button);

// レイアウト・マネージャに登録
layout.setHorizontalGroup(hGroup);

はじめに,GroupLayout#createSequentialGroupメソッドでシーケンシャル・グループを生成します。

そして,生成したSequentialGroupオブジェクトにaddComponentメソッドを使用して,コンポーネントを追加していきます。addComponentメソッドの戻り値はSequentialGroupオブジェクトなので,そのまま続けて記述できます。

このようなところが,GUIビルダーで扱いやすくなっている部分です。つまり,コードを自動生成しやすいようにメソッドが設計されているのです。

最後にsetHorizontalGroupメソッドでグループをレイアウト・マネージャに登録します。

垂直方向も同じように記述します。

// 垂直方向のグループ
GroupLayout.ParallelGroup vGroup
    = layout.createParallelGroup();

// グループにコンポーネントを追加
vGroup.addComponent(label)
      .addComponent(field)
      .addComponent(button);

// レイアウト・マネージャに登録
layout.setVerticalGroup(vGroup);
実行結果
図2 実行結果

垂直方向はパラレル・グループなので,GroupLayout#createParallelGroupメソッドを使用してParallelGroupオブジェクトを生成します。

後は水平方向と同じです。

ソースコードができあがったので,実行してみましょう(図2)。意図したようにレイアウトができました。ParallelGourpクラスは,どうやらデフォルトではセンターで整列するようです。

とはいうものの,先週解説したように,文字を表示するコンポーネントはベースラインでそろえるほうが整然と見えます。

このサンプルもベースラインで整列するように改造してみましょう。

サンプルのソースコード GroupLayoutSample2.java

ParallelGroupクラスで,ベースラインで整列させるためにはGroupLayoutクラスのcreateParallelGroupメソッドの引数にGroupLayout.Alignment.BASELINEを指定します。

// 垂直方向のグループ
GroupLayout.ParallelGroup vGroup
    = layout.createParallelGroup(
            GroupLayout.Alignment.BASELINE);

// グループにコンポーネントを追加
vGroup.addComponent(label)
      .addComponent(field)
      .addComponent(button);

// レイアウト・マネージャに登録
layout.setVerticalGroup(vGroup);
ベースラインでの整列
図3 ベースラインでの整列

これで実行した結果が図3です。ちゃんとベースラインで整列されていることがわかります。

BASELINE以外にはCENTER,LEADING,TRAILINGが定義されています。BASELINEは水平方向専用ですが,その他の定数は垂直方向の整列にも用いることができます。

次に,もう少し複雑な例を取り上げてみましょう。

名字と名前を別々に入力するGUIです。二つのラベル,二つのテキスト・フィールドから構成されています。

これをグループで構成してみます(図4)。

グループに分割
図4 グループに分割

まず水平方向(赤線)です。水平方向は全体をシーケンシャル・グループで構成します。シーケンシャル・グループの中にはラベル二つで構成するパラレル・グループと,テキスト・フィールド二つで構成するパラレル・グループを含みます。

垂直方向(青線)も全体はシーケンシャル・グループです。その中に,名字のラベルとテキスト・フィールドを含むパラレル・グループと,名前のラベルとテキスト・フィールドを含むパラレル・グループを含みます。

これをコードで表してみましょう。

サンプルのソースコード GroupLayoutSample3.java

グループ分けができれば,コードにするのは簡単です。

// 水平方向のグループ
GroupLayout.SequentialGroup hGroup
    = layout.createSequentialGroup();

// ラベルを含むパラレル・グループを追加
hGroup.addGroup(layout.createParallelGroup()
                .addComponent(lastNameLabel)
                .addComponent(firstNameLabel));

// テキスト・フィールドを含むパラレル・グループを追加
hGroup.addGroup(layout.createParallelGroup()
                .addComponent(lastNameField)
                .addComponent(firstNameField));

layout.setHorizontalGroup(hGroup);

// 垂直方向のグループ
GroupLayout.SequentialGroup vGroup
    = layout.createSequentialGroup();

// 名字のラベル,テキスト・フィールドを含む
// パラレル・グループを追加
vGroup.addGroup(layout.createParallelGroup(
                        GroupLayout.Alignment.BASELINE)
                .addComponent(lastNameLabel)
                .addComponent(lastNameField));

// 名前のラベル,テキスト・フィールドを含む
// パラレル・グループを追加
vGroup.addGroup(layout.createParallelGroup(
                        GroupLayout.Alignment.BASELINE)
                .addComponent(firstNameLabel)
                .addComponent(firstNameField));

layout.setVerticalGroup(vGroup);
実行結果
図5 実行結果

グループにグループを追加するにはGroup#addGroupメソッドを使用します。それさえわかれば,後は前のサンプルとたいして違いません。

実行結果を図5に示します。

最後にもうちょっと複雑な例を示しましょう。名字と名前の下に年齢を示すコンボボックス,そして性別を表すラジオボタンを配置してみます。ラジオボタンは横にテキストを表示できますが,上部に表示させたかったので,わざとラベルを使ってみました(図6)。

グループ構成
図6 グループ構成

グループ化のポイントは,テキスト・フィールドと同じグループに,ラジオボタンなどを含んだグループを含ませているところです。ラジオボタンを含むグループはシーケンシャル・グループで,テキスト・フィールドと同じ幅の中に二つのパラレル・グループを含めています。

このとき,二つのパラレル・グループがくっつき過ぎないように,グループの間にギャップを入れてあります。

同様に,コンボボックスの後ろにもギャップを入れて,テキスト・フィールドとは長さが異なるようにしてあります。

サンプルのソースコード GroupLayoutSample4.java

垂直方向は単純なので,水平方向のソースコードだけを示します。

GroupLayout.SequentialGroup hGroup
    = layout.createSequentialGroup();

hGroup.addGroup(layout.createParallelGroup()
        .addComponent(lastNameLabel)
        .addComponent(firstNameLabel)
        .addComponent(ageLabel)
        .addComponent(sexLabel));

hGroup.addGroup(layout.createParallelGroup(
                    GroupLayout.Alignment.LEADING)
        .addComponent(lastNameField)
        .addComponent(firstNameField)
        .addGroup(layout.createSequentialGroup()
              .addComponent(ageComboBox)
              .addGap(10, 20, 40))
        .addGroup(layout.createSequentialGroup()
              .addGap(5, 10, 20)
              .addGroup(layout.createParallelGroup(
                      GroupLayout.Alignment.CENTER)
                  .addComponent(maleLabel)
                  .addComponent(maleButton))
              .addGap(5, 10, 20)
              .addGroup(layout.createParallelGroup(
                      GroupLayout.Alignment.CENTER)
                  .addComponent(femaleLabel)
                  .addComponent(femaleButton))
              .addGap(5, 10, 20)));

layout.setHorizontalGroup(hGroup);
実行結果
図7 実行結果

ギャップを追加するにはGroupLayout#addGapメソッドを使用します。引数は最小,推奨,最大の長さになります。

コードが複雑に見えるかもしれませんが,順に追っていけば容易に理解できるはずです。

実行結果を図7に示しました。

ここまでできれば,たいていのことはできるはずです。

例えば,このレイアウトにGridBagLayoutクラスを使用すれば,コードはもっと複雑になるはずです。

簡単で,しかも柔軟なレイアウトができるのがGroupLayoutクラスの特徴なのです。

ところで,GroupLayoutクラスはNetBeansのMatisseで使われていると上で述べました。実際にこのサンプルと同じGUIを作ってみましょう。

NetBeansでの作業を図8,図9に示しました。図9は図8を拡大したものです。これを見てもわかるように,グラフィカルにGUIを組み立てることができます。

ありがたいのは,ベースラインで整列や右詰めがとても楽にできることです。図9にベースラインの補助線が表示されていますが,自動的にあのような補助線が表示され,そこにコンポーネントが吸いつくような感じになります。

完成したものが図10です。

NetBenasのGUIビルダー
図8 NetBenasのGUIビルダー
NetBenasのGUIビルダー(拡大)
図9 NetBenasのGUIビルダー(拡大)
完成したGUI
図10 完成したGUI

それでは,NetBeansが自動生成したコードも見てみましょう。以下に水平方向のコードを示します。

layout.setHorizontalGroup(
    layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 105, javax.swing.GroupLayout.PREFERRED_SIZE))
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel2)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jTextField2))
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLabel3)
                    .addComponent(jLabel4))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jComboBox1, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(10, 10, 10)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jLabel5)
                            .addComponent(jRadioButton1))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jRadioButton2)
                            .addComponent(jLabel6))))))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);

自動生成したコードなので,見づらいかもしれません。しかし,ほとんど手書きで書いたものと同じ構成になっていることがわかるはずです。違いといえば,ギャップを省略せずに記述してあることぐらいです。

GroupLayoutクラスはGUIビルダーで使用することが前提かもしれません。しかし,柔軟なレイアウトを行うことができる数少ないレイアウト・マネージャなのですから,どんどん使ってみましょう。

著者紹介 櫻庭祐一

横河電機 ネットワーク開発センタ所属。Java in the Box 主筆

今月の櫻庭

自家製桜もち

櫻庭は洋菓子しか興味がないと思ったあなた,それは間違いです(笑)。

実は和菓子も大好きなんです。

特に冬から春にかけての和菓子。花びら餅,福豆,桜もちに柏餅。どれもおいしいですよね。

ところで,最近,江戸菓子が京菓子化しているような気がしてしかたありません。例えば,桜もち。江戸菓子の白玉粉を使った桜もちが減って,道明寺が増えているような気がします。

櫻庭は白玉粉を使った桜もちが大好きなので(もちろん道明寺も好きですけど),去年は自分で作ってしまいました。意外と簡単ですよ。

これから桜の季節なので,自家製桜もちでお花見などいかがですか。