4.7 軽量コンテナ
4.7.1 不透明なコンテナ
軽量コンテナは、名前から理解できるように軽量コンポーネントのコンテナ版です。つまり、他のコンポーネントを含むことができる、ネイティブシステムのウィンドウを持たないコンポーネントです。
独自の Graphics オブジェクトを持たないコンテナがどうやって子コンポーネントを描画するのか疑問に思うかもしれませんが、そもそも軽量コンテナは必ず他のコンテナの子コンポーネントとして表示されるため、軽量コンテナが子コンポーネントを描画するときに用いる Graphics オブジェクト自体、軽量コンテナの親コンテナの Graphics オブジェクトで行われます。
Java アプリケーションやアプレットは、AWT であれ Swing であれ、表示するウィンドウだけはネイティブシステムのウィンドウとして生成しなければなりません。グラフィックを描画するためには、必ず物理的なデバイスが必要であり、それを参照する Graphics オブジェクトを 1 つは必ず保有することを表します。そのため、軽量コンテナが含まれるウィンドウやアプレットの Graphics オブジェクトを使えば、コンテナの子コンポーネントを描画することもできます。
軽量コンテナを作成するノウハウは軽量コンポーネントの作成方法とまったく同じです。Container クラスを継承し、描画が必要であれば paint() メソッドをオーバーライドします。「$4.6.2 軽量と重量の違い」でも説明したように、オーバーライドする場合はスーパークラスの paint() メソッドを呼び出すことを忘れないでください。コンテナの paint() メソッドが隠蔽されると、軽量コンポーネントが描画されなくなってしまいます。
Container クラスを直接継承したコンテナは、デフォルトのレイアウトマネージャを持っていません。アプレットなど、デフォルトのレイアウトマネージャを持つコンテナは、他の子コンポーネントや推奨サイズの関係を調整して位置やサイズを自動的に決定しましたが、レイアウトマネージャを持たないコンテナは、子コンポーネントに設定されている位置とサイズを使ってそのまま配置します。この場合、子コンポーネントの位置やサイズは常に固定されるため、コンテナのサイズが変わっても子コンポーネントが調整されることはありません。主にゲームなど、子コンポーネントを絶対座標で設定することが重要なアプリケーションはレイアウトマネージャを意図的に null に設定しますが、多くの開発者は、特別な意図がない限りレイアウトマネージャを設定することを推奨しています。
レイアウトマネージャやその設定方法については後述するため、この場では Container を継承して軽量コンテナを実装し、null のレイアウトマネージャで軽量コンテナに子コンポーネントを配置しましょう。
import java.applet.Applet; import java.awt.*; //<applet code="Test.class" width="400" height="400"></applet> public class Test extends Applet { public void init() { PanelEx panel = new PanelEx(); LabelEx label = new LabelEx("Kitty on your lap"); panel.setPreferredSize(300 , 150); panel.setBackground(Color.RED); label.setFont(new Font("Serif" , Font.BOLD , 25)); label.setForeground(Color.WHITE); label.setBounds(10 , 10 , 200 , 50); panel.add(label); add(panel); setBackground(Color.BLACK); } } class PanelEx extends Container { private Dimension preSize; public void setPreferredSize(int width , int height) { preSize = new Dimension(width , height); } public Dimension getPreferredSize() { if (preSize == null) return super.getPreferredSize(); else return preSize; } public void paint(Graphics g) { g.setColor(getBackground()); for(int y = 0 ; y < getHeight() ; y += 5) g.drawLine(0 , y , getWidth() , y); for(int x = 0 ; x < getWidth() ; x += 5) g.drawLine(x , 0 , x , getHeight()); super.paint(g); } } class LabelEx extends Component { private String label; private int margin = 5; public LabelEx(String label) { this.label = label; } public Dimension getPreferredSize() { FontMetrics fm = getFontMetrics(getFont()); return new Dimension( fm.stringWidth(this.label) + margin * 2 , fm.getHeight() + margin * 2 ); } public void paint(Graphics g) { if (label != null) { FontMetrics fm = getFontMetrics(getFont()); g.drawString(label , getWidth() / 2 - fm.stringWidth(this.label) / 2 , getHeight() / 2 + fm.getDescent() ); } g.drawRect(0 , 0 , getWidth() -1 , getHeight() - 1); } }
コード1は Container クラスを継承する PanelEx クラスが軽量コンテナを定義しています。このコンテナは、コンポーネントの背景を塗りつぶさずに網目模様で描画されます。そして、この軽量コンテナには LabelEx 軽量コンポーネントを含ませています。
まず、このプログラムから、軽量コンポーネントと軽量コンテナが透明なコンポーネントであることを証明することができます。重量コンポーネントであるアプレットウィンドウの背景は黒で塗りつぶし、その上に PanalEx が含まれていますが、軽量コンテナの背景は網目模様に描画しているため、網目の間が透明になっています。軽量コンポーネントである LabelEx も背景が透明になっています。これは、軽量コンポーネントが独自の Graphics を持たず、コンテナの Graphics オブジェクトを用いて、コンテナに直接描画しているだけであるという原理を理解してれば納得がいくでしょう。PanelEx も LabelEx も実体は存在せず、アプレットから渡された Graphics オブジェクトを使って、アプレットウィンドウに描画しているだけなのです。