SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Swing再入門

Swing再入門 JavaにおけるGUIのレイアウト

第1回 レイアウトを考える

知ってました? GroupLayout

 Swingには、もう1つ、Swing専用のレイアウトマネージャがあります。「GroupLayout」というもので、これはJavaSE 6.0から新たに用意されたものです。GroupLayoutは、縦横に規則的にコンポーネントを配置したレイアウトを作成するものです。GridLayoutに似ていますが、GridLayoutのように等間隔で全項目にコンポーネントを組み込むのではなく、指定した場所にだけ必要なコンポーネントを組み込みレイアウトすることができます。

package jp.codezine;

import java.awt.*;
import javax.swing.*;
import javax.swing.GroupLayout.Alignment;

public class SampleApp extends JFrame {
    private static final long serialVersionUID = 1L;
    
    public SampleApp(){
        this.setSize(new Dimension(300,200));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GroupLayout layout = new GroupLayout(this.getContentPane());
        this.setLayout(layout);
        // コンポーネント保管用の配列準備
        JLabel[] labels = new JLabel[5];
        JTextField[] tfields = new JTextField[5];
        JButton[] btns = new JButton[5];
        // コンポーネント作成
        for(int i = 0;i < 5;i++){
            labels[i] = new JLabel("label" + i);
            tfields[i] = new JTextField();
            btns[i] = new JButton("button" + i);
        }
        // 垂直SequentialGroupの作成
        GroupLayout.SequentialGroup vgroup =
           layout.createSequentialGroup();
        for(int i = 0;i < 5;i++){
            GroupLayout.ParallelGroup group2
                = layout.createParallelGroup(Alignment.BASELINE);
            group2.addComponent(labels[i]);
            group2.addComponent(tfields[i]);
            group2.addComponent(btns[i]);
            vgroup.addGroup(group2);
        }
        // 水平SequentialGroupの作成
        GroupLayout.SequentialGroup hgroup
            = layout.createSequentialGroup();
        GroupLayout.ParallelGroup groupLabel
            = layout.createParallelGroup();
        GroupLayout.ParallelGroup groupTField
            = layout.createParallelGroup();
        GroupLayout.ParallelGroup groupBtn
            = layout.createParallelGroup();
        for(int i = 0;i < 5;i++){
            groupLabel.addComponent(labels[i]);
            groupTField.addComponent(tfields[i]);
            groupBtn.addComponent(btns[i]);
        }
        hgroup.addGroup(groupLabel);
        hgroup.addGroup(groupTField);
        hgroup.addGroup(groupBtn);
        
        // 水平・垂直SequentialGroupの設定
        layout.setVerticalGroup(vgroup);
        layout.setHorizontalGroup(hgroup);
    }

    public static void main(String[] args) {
        new SampleApp().setVisible(true);
    }
}
GroupLayoutを使ったレイアウト。JLabel,JTextField,JButtonを5列並べてみた。
GroupLayoutを使ったレイアウト。JLabel,JTextField,JButtonを5列並べてみた。

 これがGroupLayoutを使用した簡単なサンプルです。GroupLayoutは、使い方に少しばかりクセがあります。このレイアウトマネージャを使うためには、次のような手順を踏まなければいけません。

  1. GroupLayoutを作成します。
  2. 水平方向と垂直方向のグループをそれぞれ管理するGroupLayout.SequentialGroupを作成します。これは組み込まれたグループを順番に保管し管理するものです。
  3. 用意した水平垂直用GroupLayout.SequentialGroupに、GroupLayout.ParallelGroupを作成して組み込んでいきます。コンポーネント類は、このGroupLayout.ParallelGroupに組み込んでおきます。

 GroupLayoutは、GroupLayout.SequentialGroupGroupLayout.ParallelGroupの組み合わせによってコンポーネントを管理します。GroupLayout.SequentialGroupは複数のグループを管理するグループであり、GroupLayout.ParallelGroupは複数のコンポーネントを管理するグループです。

 GroupLayoutは、垂直方向および水平方向にグループを使ってコンポーネントを管理するようになっています。例えば、ここでは3列×5行にコンポーネントを並べています。この場合、垂直方向のグループは5つのグループが組み込まれ、それぞれのグループに各行のコンポーネントが組み込まれます。同様に、水平方向のグループには3つのグループが用意され、それぞれの中に各列ごとのコンポーネントがまとめられるわけです。

 こうして垂直水平方向にグループが用意され設定されると、これらのグループをもとにして、組み込まれている各コンポーネントが配置されていくわけです。このGroupLayoutは、基本的な考え方を飲み込めれば、縦横に配置するタイプのレイアウトを非常に柔軟に作成することができます。

独自に並べる最も単純な方法は?

 レイアウトマネージャを使わず、もっと自由にコンポーネントをレイアウトしたい、という人も多いことでしょう。setLayout(null)にして、1つ1つのコンポーネントの位置と大きさを設定していけばそれは可能です。が、例えばウインドウサイズが変更されたりしたときなど、対応するのが面倒になることもあります。

 こういうとき、どういうやり方をしますか? 「独自にレイアウトマネージャを作れ」という声が聞こえそうですが、実はもっと単純にレイアウトを調整する方法があります。それは、ContainerListenerとComponentListenerを用意する方法です。

 コンテナに組み込まれたコンポーネントのレイアウトというのは、「コンポーネントが追加・削除されたとき」「コンテナがリサイズされたとき」に再レイアウトする必要が生じます。ということは、こうしたイベントが発生したときにコンポーネント類を並べなおすようにしていれば、レイアウトマネージャなど使わずともちゃんとコンポーネント類はレイアウトされることになるはずです。

package jp.codezine;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class SampleApp extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;
    
    public SampleApp(){
        this.setSize(new Dimension(300,200));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(null);
        this.getContentPane().addComponentListener(
            new MyComponentAdapter(this));
        this.getContentPane().addContainerListener(
            new MyContainerAdapter(this));
        
        actionPerformed(null);
    }
    
    public void actionPerformed(ActionEvent e){
        JButton btn = new JButton("button");
        btn.setFont(new Font("Serif",Font.PLAIN,18));
        btn.addActionListener(this);
        this.add(btn);
    }
    
    public void doLayout() {
        super.doLayout();
        Component[] components = this.getContentPane().getComponents();
        int insetSize = 20;
        int w = (this.getContentPane().getWidth() - insetSize * 2) / 2;
        int h = (this.getContentPane().getHeight() - insetSize * 2);
        int insetW = w / components.length;
        int insetH = h / components.length;
        int x = insetSize;
        int y = insetSize;
        for(Component c: components){
            c.setBounds(new Rectangle(x,y,w,insetH));
            x += insetW;
            y += insetH;
        }
    }

    public static void main(String[] args) {
        new SampleApp().setVisible(true);
    }
}

class MyContainerAdapter extends ContainerAdapter {
    private SampleApp sampleapp;
    
    public MyContainerAdapter(SampleApp app){
        super();
        this.sampleapp = app;
    }
    
    @Override
    public void componentAdded(ContainerEvent e) {
        sampleapp.doLayout();
    }
    @Override
    public void componentRemoved(ContainerEvent e) {
        sampleapp.doLayout();
    }
    
}

class MyComponentAdapter extends ComponentAdapter {
    private SampleApp sampleapp;
    
    public MyComponentAdapter(SampleApp app){
        super();
        sampleapp = app;
    }
    
    @Override
    public void componentResized(ComponentEvent e) {
        sampleapp.doLayout();
    }
}
レイアウトマネージャを使わず、イベントを使ってレイアウトさせる例。起動時にはボタンが1つだけ表示される。
レイアウトマネージャを使わず、イベントを使ってレイアウトさせる例。起動時にはボタンが1つだけ表示される。
ボタンをクリックするとボタンが次々に増えていく。触れると自動的にボタンの位置と大きさが調整される。
ボタンをクリックするとボタンが次々に増えていく。触れると自動的にボタンの位置と大きさが調整される。

 これは、ごく単純なレイアウト調整の例です。ComponentListener/ContainerListenerをそれぞれ用意して組み込み、その中でコンポーネントやコンテナのイベントに応じてdoLayoutを実行して再レイアウトを行わせています。ここでは、コンポーネントを左上から右下にかけて斜めに配置させています。一応、再レイアウトされていることが分かるように、JFrameの横幅にあわせてコンポーネントの幅を調整するようにしておきました。

 コンポーネントの追加と削除およびウインドウのリサイズ時に、doLayoutを呼び出して再レイアウトするようになっています。このように、再レイアウトのためのメソッドを用意しておき、イベントが起こった際にこれを呼び出してレイアウトし直せば、常に正しいレイアウト状態を保つことができます。

 この「レイアウトマネージャを使わず、コンテナのイベントを使ってレイアウトする」という方法は、いわばAWT/Swingのレイアウトの根本を否定するようなやり方ですから、あまりほめられたものではありませんが、ちょっとしたテクニックとして覚えておいても損ではないでしょう。

次のページ
レイアウトマネージャ、作れますか?

この記事は参考になりましたか?

Swing再入門連載記事一覧

もっと読む

この記事の著者

掌田 津耶乃(ショウダ ツヤノ)

三文ライター&三流プログラマ。主にビギナーに向けたプログラミング関連の執筆を中心に活動している。※現在、入門ドキュメントサイト「libro」、カード型学習サイト「CARD.tuyano.com」を公開...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1626 2007/08/29 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー