LayoutManager2は使えますか?
LayoutManagerの場合、addする際に渡せるのはString値だけでした。より高度なレイアウトを考えるならば、addする際にレイアウトのためのオブジェクトを渡して、それを元にレイアウトできるようにしたいものです。これを行えるのが、LayoutManagerを拡張したLayoutManager2です。これは、LayoutManagerのメソッドに加え、さらにいくつかのメソッドを用意する必要があります。
package jp.codezine; import java.awt.*; import java.awt.event.*; import java.util.*; 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(new MyLayout()); actionPerformed(null); } public void actionPerformed(ActionEvent e){ int n = new Random().nextInt(200) + 100; JButton btn = new JButton("button [" + n + "]"); btn.setFont(new Font("Serif",Font.PLAIN,18)); btn.addActionListener(this); this.add(btn,new Integer(n)); } public static void main(String[] args) { new SampleApp().setVisible(true); } } class MyLayout implements LayoutManager2 { private Container container; private ArrayList<Component> components; private ArrayList<Integer> widths; public MyLayout(){ components = new ArrayList<Component>(); widths = new ArrayList<Integer>(); } @Override public void addLayoutComponent(String name, Component comp) { try { this.addLayoutComponent( comp,new Integer(Integer.parseInt(name))); } catch (NumberFormatException e) { e.printStackTrace(); } } @Override public void removeLayoutComponent(Component comp) { int n = -1; for (int i = 0;i < components.size();i++){ if (components.get(i) == comp) n = i; } components.remove(comp); widths.remove(n); if (container != null) container.doLayout(); } @Override public void layoutContainer(Container parent) { container = parent; int insetSize = 20; int h = (parent.getHeight() - insetSize * 2); int size = components.size() == 0 ? 1 : components.size(); int insetH = h / size; int x = insetSize; int y = insetSize; for(int i = 0;i < components.size();i++){ Component c = components.get(i); int w = widths.get(i); c.setBounds(new Rectangle(x,y,w,insetH)); y += insetH; } } @Override public Dimension minimumLayoutSize(Container parent) { return new Dimension(100,100); } @Override public Dimension preferredLayoutSize(Container parent) { return parent.getPreferredSize(); } @Override public void addLayoutComponent( Component comp, Object constraints) { components.add(comp); try { widths.add((Integer)constraints); } catch(ClassCastException ex){ widths.add(0); } if (container != null) container.doLayout(); } @Override public float getLayoutAlignmentX(Container target) { return 0; } @Override public float getLayoutAlignmentY(Container target) { return 0; } @Override public void invalidateLayout(Container target) {} @Override public Dimension maximumLayoutSize(Container target) { return new Dimension(target.getMaximumSize()); } }

ここでは先ほどのMyLayoutを改良し、横幅をIntegerインスタンスで渡すようにしてみました。コンポーネントの組み込み部分を見ると、this.add(btn,new Integer(n));となっていることが分かるでしょう。LayoutManager2から追加されたメソッドを整理すると次のようになります。
- public void addLayoutComponent(Component comp, Object constraints)
- public float getLayoutAlignmentX(Container target)
- public float getLayoutAlignmentY(Container target)
- public void invalidateLayout(Container target)
- public Dimension maximumLayoutSize(Container target)
addの際に、StringではなくObjectを渡してレイアウトするためのメソッドを提供するものです。Containerの最大サイズを返します。 LayoutManager2の最大のポイントは、addLayoutComponentメソッドでしょう。ここでレイアウトを行わせます。ここではIntegerを渡していますが、より細かな設定を行いたいのであれば、レイアウトの数値を保持するクラスを定義して、そのインスタンスとして渡すとよいでしょう。これは、GridBagLayoutなどの利用例を思い出すとイメージしやすいのではないでしょうか。
まとめ
今回は、コンポーネントのレイアウトについて考えてみました。Swing用に用意されたレイアウトマネージャなどはまだまだなじみがないのではないでしょうか。それ以外のコンポーネントやコンテナのイベントを使った方法や、レイアウトマネージャの自作などは、AWTでもそのまま利用できるものです。
レイアウトマネージャによるレイアウトは、JavaのGUIを始めたときに最初に遭遇する関門といってよいでしょう。凝ったレイアウトをするのに、ついsetLayout(null)してしまうクセがついてしまっている人、多いんじゃないでしょうか。レイアウトマネージャをnullにし、すべて手作業で組み込むのは、確実ではありますが刹那的な方法です。もし、何らかの規則性が見出せるのであれば、レイアウトマネージャとして定義しておけば後々応用が利きます。
似たようなレイアウト処理を何度もやってるな……と感じたら、一度、レイアウトの方法を考え直してみてはいかがでしょう。
