AWTやSwingのコンポーネントを自由に使う1つのポイントは、レイアウト機能について理解を深めることです。座標を指定して配置すればよいではないかと思うかもしれませんが、レイアウト機能を使うと、より複雑なことをしたいときにも、非常に簡単に対処できることもあるので、Javaの特徴でもあるこのレイアウト機能をぜひとも活用するようにしましょう。
AWTでは、コンポーネントをウィンドウに配置するときに利用できる機能として「レイアウト機能」を用意しています。非常にシンプルなやり方で、コンポーネントをウィンドウの中にうまく配置するということを実現しています。単に配置するだけなら、座標値を指定して配置するのと同じだし、そのほうが簡単だと思うかもしれません。しかしながら、Javaで作ったプログラムは、どんな環境で動かされるかは判別できない場合もあるのです。そうすると、動作環境上の微妙な違いが出てきて、意図しない結果にもつながります。レイアウト機能をうまく使うと、そうした違いをある程度吸収できなくもありません。もちろん、レイアウト機能の特性をうまく利用するのが前提です。
また、あとで実例を示しますが、ウィンドウに表示したとき、ウィンドウのサイズ変更に対してコンポーネントの位置が追随するような動作まで可能になっています。アプリケーションとして作ったもので、ウィンドウに何かを表示する場合には、レイアウト機能を使うことで、何もしなくてもウィンドウのサイズ変更に追随できるようになるというメリットもあります。
レイアウト機能には、表のようにいくつかの種類があります。ウインドウはBorderLayoutが既定値となっており、何も指定しなければこのレイアウト機能が使えるようになっています。「レイアウト機能」の列の名前は、そのレイアウトの機能を提供するクラス名です。Javaの世界ではこうしたレイアウト機能がクラスとして定義されていて、プログラムの中で利用できます。
レイアウト機能 | 特徴 |
---|---|
BorderLayout | 表示領域の周辺にコンポーネントを配置できる。ウインドウの既定のレイアウト機能 |
CardLayout | ページ切り替えができる表示領域 |
FlowLayout | コンポーネントを左から右、上から下へと順番に配置する |
GridLayout | 行列に分割してそれぞれにコンポーネントを配置する |
BridBagLayout | 行列に分割してそれぞれにコンポーネントを配置する。GridLayoutよりも柔軟に領域を設計できる |
新しいプログラムを実行する前に、前の章のプログラムのFrameByDesigner.javaを再度見て下さい。簡単にまとめると、コンポーネントをnewキーワードでインスタンス化して、addというメソッドでウインドウに追加でき、それは、追加した順序にウインドウ内で並んでいます。前の章では、レイアウト機能を使わないようにして、とりあえずマウスで指定した位置にボタンなどを配置しました。
ここで、FramaByDesigner.javaを表示し、下側のタブで「設計」を選択してビジュアルデザイナで表示します。そして、背景部分をクリックして選択し、右側の「プロパティ」のリストで、Layoutの右側のところのドロップダウンリストから「FlowLayout」を選択します。
そうすると、ボタンなどの位置がさっと変化してしまうはずです。せっかく並べたのにと思うかも知れませんが、ここでは、コンポーネントがまず上から順番に並ぶことを見て下さい。ウインドウの中を、あたかもワープロの文字の並びのように左から右に順番に並びます。
ここで、プログラムを実行させます。すると、ウインドウが表示され、設計画面と同じようにコンポーネントが見えています。ウインドウの右下の部分をドラッグするなどして、ウインドウの大きさをいろいろな大きさに変更してみましょう。すると、ウインドウの大きさに合わせて、コンポーネントのレイアウトの配置が変化します。
FlowLayoutでは、プログラム中で座標値などを指定しなくても、レイアウト機能の管理下で、アプレット内に配置されます。FlowLayoutなので、左から右、上から下へ、addした順番に並ぶということになります。「ソース」のタブに切り替えてプログラムを見て下さい。setLayoutでFlowLayoutが指定されていますが、コンポーネントはnewで生成された後は、addでアプレットに追加されるだけで、座標値の指定らしきものはありません。
このような結果になることがよいか悪いかはさておいて、機能面ではおもしろいと感じられるのではないでしょうか。ウィンドウのサイズを変更したときの処置をプログラム内に何も記述していないにもかかわらず、ウィンドウのサイズ変更で、見え方がダイナミックに変わってしまいます。ちょうどテキストエディタで、テキストを折返しで表示しているような具合です。
実は、Javaを実行する環境によってボタンの大きさとかテキストフィールドの大きさは微妙に違っていたりするので、FlowLayoutだと、あるJava VMでは1行目に3つのコンポーネントがあったとしても、同じプログラムを別のJava VMで実行すると、1行目には4つのコンポーネントが配置されるかもしれないのです。その意味では、FlowLayoutは実用にするとなると少し考えたくなるかもしれませんが、手軽なレイアウト機能としてはそれなりに使い手はあります。たとえば、BorderLayoutの1つのパートに複数のコンポーネントを配置する時に、FlowLayoutを使うということも考えられます。
ウインドウの既定値となっているBorderLayoutについて紹介しましょう。ウィンドウのレイアウト機能の既定値となっているように、ウィンドウ表示を考慮したものであり、実用度はFlowLayoutよりも高いでしょう。一般的なアプリケーションでは、ウインドウ内にメニューバーやツールバーがありますが、そうしたプログラムを作る時にはBorderLayoutを使うと簡単にコンポーネントを配置できます。
実際にBorderLayoutを利用したプログラムを作ってみましょう。レイアウト機能のチェックの続きなので、前の章で作った「SwingGUI.jpr」のプロジェクトに追加します。先週のプロジェクトが開いた状態で次のように作業をします。ここでは「クラス」を作ることで、新たなウインドウを作る方法を説明します。
1ツールバーの「新規」ボタンをクリックすると、新しく作るオブジェクトの選択ウインドウが表示されます。ここで「クラス」を選択して、OKボタンをクリックします。(「ファイル」メニューから「新規クラス」を選択してもかまいません。)
2クラスウィザードが表示されます。パッケージ名はそのままにします。クラス名は「BorderLayoutWindow」と入力します。ベースクラスでは、テキストボックスの右側の「…」ボタンをクリックします。
3「ベースクラスの選択」ウインドウが表示されます。javax→swingと階層表示をたどり、JFrameを選択してOKボタンをクリックします。
4ベースクラスに「javax.swing.JFrame」と入力されたことを確認してOKボタンをクリックします。
5新たに「BorderLayoutWindow.java」プロジェクトに追加されていることを確認します。「BorderLayoutWindow」が右側に表示されていない場合は、左側で「BorderLayoutWindow.java」をダブルクリックして表示します。そして、 プログラムを次のように変更します。
package swinggui; import javax.swing.*; import java.awt.*; /** * <p>タイトル: </p> * <p>説明: </p> * <p>著作権: Copyright (c) 2005</p> * <p>会社名: </p> * @author 未入力 * @version 1.0 */ public class BorderLayoutWindow extends JFrame { private JPanel contentPane; public BorderLayoutWindow() { contentPane = (JPanel) this.getContentPane(); this.setSize(new Dimension(400, 300)); this.setTitle("クラスとして作ったウインドウ"); } // public boolean postEvent(Event evt) { /**@todo この java.awt.MenuContainer abstract メソッドを実装*/ // throw new java.lang.UnsupportedOperationException("メソッド postEvent() は、まだ実装されていません。"); // } // public Font getFont() { /**@todo この java.awt.MenuContainer abstract メソッドを実装*/ // throw new java.lang.UnsupportedOperationException("メソッド getFont() は、まだ実装されていません。"); // } }
6このプロジェクトのアプリケーションのクラスとなっているUseComponent.javaのソースを開きます。そして、図にあるように、一部にプログラムを追加します。public UseComponents() { の次に2行を加えます。つまり、UseComponentのコンストラクタにプログラムを2行加えます。
BorderLayoutWindow blWindow = new BorderLayoutWindow(); blWindow.setVisible(true);
7実行してみます。新たなクラスによるウインドウが、画面の左上に表示されました。問題なく実行できることが確認できれば、実行して表示されたウインドウを閉じておきます。
では、作成した「BorderLayoutWindow.java」にコンポーネントを加えてみましょう。次のように作業をします。
1右側のパネルに「BorderLayoutWindow.java」が表示されているのを確認します。(表示されていない場合には、左のリストから項目名をダブルクリックします。)そして、下側の「設計」タブをクリックします。
2ビジュアルデザイナのウインドウが開きます。右側の「プロパティ」のパネルで、Layoutプロパティを「BorderLayout」に変更します。
3ツールパレットにあるボタンのツールを選択して、パネルの中央付近をドラッグし、ボタンを作ろうとします。
4なぜかウインドウ全体を占めるボタンができてしまいました。おかしいと思うかも知れませんが、これは正常な動作です。
5再度、ボタンのツールを選択して、ウインドウの上部付近をドラッグしてボタンを作成します。ドラッグ範囲はだいたいでいいですが、図を参考にして下さい。
6新しいボタン(名前はjButton2)が作成されましたが、アプレットの上部にきっちりとくっついた位置に勝手に配置されました。
7ボタンのツールを選択し、今度はアプレットの左側のあたりをドラッグして、ボタンを作成してみます。
8左端にぴったりとくっついて配置される新しいボタン「jButton3」が作成されました。
9ボタンのツールを選択し、アプレットのパネルの下の方でドラッグしてボタンを作成します。
10下側にぴったりとくっついたボタンjButton4が作成されました。
11ボタンのツールを選択し、アプレットのパネルの右端の方でドラッグしてボタンを作成します。パネルが全部見えていないのなら、適当にスクロールをしてください。
12右端にぴったりとくっついて配置される新しいボタンのjButton5が作成されました。
こうして、BorderLayoutの機能を割り当てたアプレットに、ボタンをとにかく配置しました。配置した結果は、ルーズにドラッグをしても、ある決められた領域に吸い付くように配置されています。こうした配置ができるのがBorderLayoutの特徴です。何と表現したらよいかは迷うところですが、ローマ数字の「Ⅱ」の文字のように、ウィンドウ内が区分けされていると言えばよいでしょうか。それぞれの区分け内では、ボタンがすっぽりと覆うように配置されています。ウィンドウはボタンで埋められていますが、プログラム上ではボタンのサイズや位置などを指定した形跡がないことをまず確認しておいてください。
実行して、ウインドウの大きさを変更してみてください。極端な場合は別として、たとえばjButton2は高さが一定ですが、幅はウインドウ幅に追随されます。こうした大きさの調整が全自動で行われているのです。
ここで、配置したボタンを選択してプロパティを確認しましょう。ボタンのプロパティに太字のconstraintsというものがあります。jButton1のボタンのconstraintsプロパティは「Center」となっています。jButton2はNorth、jButton3はWest、jButton4はSouth、jButton5はEastになっています。東西南北を示すプロパティですが、これはボタンがレイアウトで分割されるどの位置に配置されるのかを示すものとなっています。
プログラムのBorderLayoutWindow.javaを見てください。とは言っても、今回のプログラムは、1行もキータイプをしていません。ビジュアルデザイナの操作をするだけで、必要なプログラムがすべて作られています。右側の表示領域で「ソース」のタブをクリックすると、生成されたプログラムを参照することができます。そこから、ポイントになる部分をかいつまんで説明しましょう。
まず、クラス全体の構造をみます。名前はもちろん、「BorderLayoutWindow」ですが、JFrameつまり、ウインドウをもとにして作られているクラスであることが分かります。そして、いくつかのメソッドがあります。どんなメソッドがあるかをリストアップしてみましょう。
package swinggui; import javax.swing.*; import java.awt.*; public class BorderLayoutWindow extends JFrame { /* インスタンス変数の定義 */ public BorderLayoutWindow() { } private void jbInit() throws Exception { } }
続いて、インスタンス変数の定義をみてみます。次のようになっています。ここで、レイアウトのプロパティを指定したこともあって、BorderLayoutクラスのインスタンスが作成されて、変数borderLayout1から参照できるようになっています。レイアウトの機能自体もやはりクラスであり、実際にはインスタンスを生成して利用しなければなりません。そこで、newキーワードでインスタンスを生成します。それを、変数borderLayout1に代入していますが、この変数定義はメソッドの外にあります。従って、このクラス内では共通に使える変数(インスタンス変数)として利用できます。
そして、ボタンを5つ作りましたが、それぞれのボタンはJButtonクラスのインスタンスです。jButton1など5つのインスタンス変数が定義されており、その段階ですでにインスタンスが生成されていて、そのインスタンスへの参照が保存されています。結果的にクラスの中からはどこからでもボタンを利用することができます。すべて、privateというキーワードがついていますが、これは、「このクラスの中からだけしか使えない」ということを意味します。
private JPanel contentPane; private BorderLayout borderLayout1 = new BorderLayout(); private JButton jButton1 = new JButton(); private JButton jButton2 = new JButton(); private JButton jButton3 = new JButton(); private JButton jButton4 = new JButton(); private JButton jButton5 = new JButton();
jbInit()というメソッドが新たに定義されています。このメソッドは、BorderLayoutWindowクラスのコンストラクタから呼び出されているので、つまり、BorderLayoutWindowクラスのインスタンスを作ったときに、呼び出されるはずです。文字通り、Initという言葉から「初期化のためのもの」と考えればいいのですが、jbをつけておいてJBuilderが作ったものであることを明示しているのです。なお、try、Exceptionなどのキーワードはエラーがあったときの処理を記述するものです。一連のテキストでは解説はしませんので、このあたりは無視して、コンストラクタにjbInitの呼び出しがあることだけをみてください。jbInitメソッドをみてみましょう
private void jbInit() throws Exception { this.getContentPane().setLayout(borderLayout1); jButton1.setText("jButton1"); jButton2.setText("jButton2"); jButton3.setText("jButton3"); jButton4.setText("jButton4"); jButton5.setText("jButton5"); this.getContentPane().add(jButton1, BorderLayout.CENTER); this.getContentPane().add(jButton2, BorderLayout.NORTH); this.getContentPane().add(jButton3, BorderLayout.WEST); this.getContentPane().add(jButton4, BorderLayout.SOUTH); this.getContentPane().add(jButton5, BorderLayout.EAST); }
ここでのthisというのは、このプログラムを含むクラス、つまりウインドウそのものを参照します。ウインドウの基本的な描画領域を取り出すのが、getContantPaneというメソッドです。それに対して、setLayoutというメソッドを利用することで、ウインドウで使うレイアウト機能を指定します。変数borderLayout1は、クラスをインスタンス化したときにすでにインスタンスが作られてそこへの参照が代入されています。
クラスに用意されているメソッドを使うときには、thisというキーワードにメソッドを記述することを説明しました。実はthisは省略できます。言い換えれば、いきなりメソッドを記述すると、そのプログラムを含むクラスのインスタンスに対してメソッドを適用するということになります。したがって「this.getContantPane().setLayout(...);」は正しいのですが、「getContantPane().setLayout(...);」も正しいのです。むしろ、後者の書き方のほうがよく行われるので、省略すると自分自身に対してそのメソッドを適用しているということを理解しておいてください。
ボタンについてもjbInitでの処理をみて行きます。setTextは、ボタンに実際に見える文字列を設定します。引数に設定した「jButton1」という文字がボタンに表れるということになります。そして、addメソッドでボタンをウインドウに組み込みますが、「this.getContentPane()」で得られたアプレットの基本描画領域に、jButton1をaddしています。addメソッドの1つ目の引数に、追加するコンポーネントを指定します。そして、BorderLayoutのときには、addメソッドの2つ目の引数も指定しなければなりません。この2つ目の引数で、BorderLayoutのどこにボタンを配置するかを指定します。「BorderLayout.CENTER」という記述は説明をすると複雑になりますので、ここでは決められたキーワードだと考えておいて下さい。BorderLayoutにaddするときに指定できるキーワードは、次の通りです。
ところで、North部分の高さや、Eastの幅などは、うまくボタンの文字が隠れないようになっていると思いませんか? 実は、AWTやSwingのコンポーネントは「大きさ」という情報だけでなく「望ましい大きさ」「最小の大きさ」という情報も持っています。BorderLayoutでは、こうした情報を利用して、コンポーネントを配置します。たとえば、Northでは、そこに配置したコンポーネントの望ましい大きさの高さ分の領域を取り、横幅はこの場合だとウィンドウ幅いっぱいに取ります。Southも同様です。EastやWestは、望ましい大きさの幅の情報を使って、それらの領域の幅を決め、高さは、Northの下からSouthの上までを占めるように設定されます。そして、残りの場所をCenterが占めるという具合です。結果的にはうまくいっているように見えるのですが、その背後の機能について知っておいても損はないでしょう。
なお、この2つ目の引数は、以前は文字列を直接指定していました。「"Center"」などと指定をしていたのですが、ここで大文字と小文字の組み合わせなんて、すぐ忘れてしまい、毎度毎度調べていたりしました。しかしながら、そうした手法ではなく、ここで示したようにキーワードで指定する方法が一般的になっています。キーワードなら、クラス名のBorderLayoutと、単語のCENTERでいいわけです。クラス名は単語の先頭だけが大文字で残りは小文字になります。後半の単語はここでは全部大文字なので、どちらかと言えば忘れにくくはなったかと思います。
BorderLayoutの機能を確認するために、ボタンをCenterの領域にさらに1つ追加してみましょう。「設計」タブをクリックしてビジュアルデザイナを呼び出し、ボタンのツールをクリックして中心付近をドラッグしてボタンを生成してみます。すると、Centerの領域に2つのボタンが組み込まれるわけではなく、新たに作成したボタンの方が残り、前からあるボタンは消えてしまっています。
プログラムを細工して、同じパートに複数のコンポーネントをaddで追加するようにしても実際にプログラムはエラーは出さないで動いてくれます。しかしながら、実際には最後に追加したコンポーネントしかウインドウには組み込まれていません。このように、BorderLayoutでは、5つある部分のそれぞれでは、最後にaddした1つのコンポーネントしか表示されないようになっています。
そうなるとなんて不便なんだと思うかもしれませんが、心配はいりません。BorderLayoutという機能を利用しながら、複数のコンポーネントを効果的に配置するということができます。そのポイントになるのはPanelあるいはJPanelというコンポーネントです。名前から想像できるように、単にコンポーネントを配置できる、まさに“パネル”なのです。たとえば、BorderLayoutの1つの部分に、まず、Panelだけを配置します。そして、配置したパネルの中に、ボタンをいくつか配置するということが行えます。コンポーネントを階層的に配置するものと思えばよいでしょう。こうしたやり方ができるので、かなり自由度は高くなってきます。
また、このように5つも分割している必要は全然ないと思うかもしれません。それも安心してください。試しに、特定のパートへのaddの部分をコメントにして、addをしないでプログラムを実行してみてください。例えば、Southへのaddをやめると、ウィンドウの下端に貼り付く部分はなくなり、他のパートで埋められます。たとえば、ウィンドウいっぱいに1つのコンポーネントを配置したいのであれば、Centerだけに配置すれば、それですみます。その場合、他のパートは一切画面には登場しないのです。こうした使い方までを考えれば、BorderLayoutはいかに有用であるかは理解していただけるのではないかと思います。
10-1:ビジュアルデザイナを使って電卓の画面を作ることにする。ウインドウをBorderLayoutにして、NorthパートにJTextFieldを配置する。そして、CenterパートにJPanel、SouthパートにJButtonを配置する。East、Westには何も配置しない。CenterのパートのJPanelをさらにGridLayoutにして4行4列の16分割にして、16個のボタンを配置する。Southのボタンは「=」ボタンとし、Centerのボタンは適当に0〜9、小数点、四則演算、クリアと名前を付けること。実際に電卓の機能までは実現しなくてもいい。見栄えだけのウインドウを作ること