Swing:GroupLayoutを簡単に作るためのビルダ

あまりにSwingのGroupLayoutがわかりにくいので、これを簡単にするためのビルダを作ってみた。

実行サンプル

まずは実行サンプルだ。

以下のようなコードで、

import javax.swing.*;
import javax.swing.GroupLayout.*;

public class GroupLayoutBuilderTest {

  public static void main(String[]args) {
    JFrame frame = new JFrame();
    new GroupLayoutBuilder()
        .addRow(Alignment.BASELINE, new JLabel("ユーザ名を入力"), null, new JTextField())
        .addRow(Alignment.BASELINE, new JLabel("パスワード"), new JButton("詳細"), new JTextField(), new JLabel("test"))
        .setColAlign(Alignment.TRAILING, Alignment.LEADING)
        .build(frame.getContentPane());
    frame.setBounds(200, 200, 400, 400);   
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
  }
}

次の結果になる。

コード

本体コードは以下だ。

import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.stream.*;

import javax.swing.*;
import javax.swing.GroupLayout.*;

/*
 * {@link GroupLayout}を簡単に作るためのビルダ
 */
public class GroupLayoutBuilder {

  /** 全行コンテナ */
  private Rows rows = new Rows();


  /** 列アラインメント */
  private Aligns colAligns = new Aligns(Alignment.LEADING);

  /** 各列アラインメントを指定する。指定の無いときは{@link Alignment.LEADING}になる。 */
  public GroupLayoutBuilder setColAlign(Alignment...colAligns) {
    this.colAligns = new Aligns(colAligns);
    return this;
  }

  /** 行コンポーネントを追加する */
  public GroupLayoutBuilder addRow(Component...cols) {
    return addRow(Alignment.LEADING, cols);
  }

  /** 
   * 行アラインメント付きで行コンポーネントを追加する
   * 上下に余裕がある場合に引き伸ばされたくない場合は{@link Alignment.BASELINE}
   * を指定すること。
   * @param align 行アラインメント
   * @param cols 列コンポーネント
   * @return 本オブジェクト
   */
  public GroupLayoutBuilder addRow(Alignment align, Component...cols) {
    rows.add(new Row(align, cols));
    return this;
  }

  /* GroupLayoutのオプション */

  private boolean autoCreateGaps = true;
  private boolean autoCreateContainerGaps = true;

  /**
   * コンポーネント間のギャップを自動的に作成するか。デフォルトはtrue
   * @param value コンポーネント間のギャップを自動的に作成するか
   * @return 本オブジェクト
   */
  public GroupLayoutBuilder setAutoCreateGaps(boolean value) {
    this.autoCreateGaps = value;
    return this;
  }

  /**
   * 他のコンテナと、このコンテナのボーダーに接するコンポーネント間のギャップを、
   * 自動的に作成するか。デフォルトはtrue
   * @param value ギャップを自動的に作成するか
   * @return 本オブジェクト
   */
  public GroupLayoutBuilder setAutoCreateContainerGaps(boolean value) {
    this.autoCreateContainerGaps = value;
    return this;
  }


  /**
   * ビルドする
   * @param <T> ターゲットコンテナの型
   * @param target ターゲットとするコンテナ
   * @return ターゲットとするコンテナ
   */
  public <T extends Container>T build(T target) {

    // 各行を異なる列数にはできない。統一するために空のラベルを挿入
    int maxColCount = rows.maxColCount();
    rows.getRowStream().forEach(row->row.forceColCount(maxColCount));

    // ターゲットに対するレイアウトを作成し、ターゲットに設定する
    GroupLayout layout = new GroupLayout(target);
    layout.setAutoCreateGaps(this.autoCreateGaps);
    layout.setAutoCreateContainerGaps(this.autoCreateContainerGaps);
    target.setLayout(layout);

    // 垂直グループを作成する
    makeVerticalGroup(layout);

    // 水平グループを作成する
    makeHorizontalGroup(layout);

    return target;
  }

  /** 
   * 垂直グループを作成する
   * @param layout
   */
  void makeVerticalGroup(GroupLayout layout) {

    // 垂直グループを作成し、レイアウトに設定
    SequentialGroup rowSeq = layout.createSequentialGroup();    
    layout.setVerticalGroup(rowSeq);

    // 各行について処理
    rows.getRowStream().forEach(row-> {
      ParallelGroup rowGroup = layout.createParallelGroup(row.align);
      rowSeq.addGroup(rowGroup);      
      row.stream().forEach(c->rowGroup.addComponent(c));
    });    
  }

  /** 水平グループを作成する */
  void makeHorizontalGroup(GroupLayout layout) {

    // 水平グループを作成し、レイアウトに設定
    SequentialGroup colSeq = layout.createSequentialGroup(); 
    layout.setHorizontalGroup(colSeq);

    int maxCols = rows.maxColCount();
    IntStream.range(0,  rows.maxColCount()).forEach(col-> {
      ParallelGroup colGroup = layout.createParallelGroup(colAligns.get(col));
      rows.getColStream(col).forEach(c-> {
        colGroup.addComponent(c);
      });
      colSeq.addGroup(colGroup);
    });
  }

  /** アラインメント */
  static class Aligns {
    Alignment defaultAlign;
    List<Alignment>list = new ArrayList<>();

    public Aligns(Alignment defaultAlign) {
      this.defaultAlign = defaultAlign;
    }

    public Aligns(Alignment[]values) {
      list = Arrays.stream(values).collect(Collectors.toList());
    }

    public Alignment get(int index) {
      if (list.size() <= index) return Alignment.LEADING;
      return list.get(index);
    }
  }

  /** 全行コンテナ */
  static class Rows {

    List<Row>rows = new ArrayList<>();

    /** 行を追加する */
    void add(Row row) {
      rows.add(row);
    }

    /** 指定列のセルストリームを取得する */
    Stream<Component>getColStream(int colIndex) {
      return rows.stream().map(row->row.get(colIndex));      
    }

    /** 全行ストリームを取得する */
    Stream<Row>getRowStream() {
      return rows.stream();
    }

    /** 最大列数を取得する */
    int maxColCount() {
      return rows.stream().mapToInt(r->r.colCount()).max().getAsInt();
    }
  }

  /** 行コンテナ */
  static class Row {

    Alignment align;
    List<Component>cols;

    /** 列コンポーネントを指定する。nullの部分には空のラベルを入れる */
    Row(Alignment align, Component...cols) {
      this.align = align;
      this.cols = Arrays.stream(cols)
        .map(c-> c == null? newEmpty():c)
        .collect(Collectors.toList());
    }

    /** 列ストリームを取得する */
    Stream<Component>stream() {
      return cols.stream();
    }

    /** 指定列数に強制する */
    void forceColCount(int colCount) {
      while (cols.size() < colCount) 
        cols.add(newEmpty());      
    }

    /** 列数 */
    int colCount() {
      return cols.size();
    }

    /** 指定列のコンポーネントを取得する */
    Component get(int colIndex) {
      if (cols.size() <= colIndex) return null;
      return cols.get(colIndex);
    }

    private Component newEmpty() {
      return new JLabel("");
    }
  }
}