実験室 |
レイアウトマネージャを作ってみよう |
||
コンポーネントのレイアウトはめんどうくさい |
||
GUI をあつかうときにさけて通れないのが、コンテナにコンポーネントをどのように配置するかということです。この死語とはレイアウトマネージャが行うことです。 レイアウトマネージャには JDK 1.0 の頃からあった FlowLayout や BorderLayout などから、J2SE 1.4 でとり入れられた SpringLayout まで各種あります。また、これからもさまざまなレイアウトマネージャが提供されると思います。 しかし、しかし、本当にほしいレイアウトマネージャってないと思いません ? どれも帯に短し、たすきに長しで、これぞ決定版というのがなかなかありません。結局、使いづらい GridBagLayout をひいこらいって使うか、setLayout(null) としてしまうことも多いのではないでしょうか。 そんなときには作ってしまいましょう。 以外と簡単ですよ。
|
レイアウトマネージャを作るための準備 |
||
使うときにはまったく意識しなくてもいいのですが、実をいうとレイアウトマネージャには 2 種類あります。 1 つは LayoutManager インタフェースをインプリメントしたもの。 もう 1 つは LayoutManager2 インタフェースをインプリメントしたものです。 さて、この 2 つは何が違うのでしょう。 LayoutManager インタフェースをインプリメントしたクラスには FlowLayout, GridLayout などがあります。LayoutManager2 インタフェースをインプリメントしたクラスには BorderLayout, GridBagLayout, SpringLayout などがあります。 2 つの違いは、LayoutManager インタフェースの方は Container#add(Component comp) メソッドだけを使用するのに対し、LayoutManager2 インタフェースの方は Container#add(Component comp, Object constraints) メソッドを使用するか、XXXXXConstraints クラスをレイアウトマネージャと一緒に使うという違いです。 たとえば、BorderLayout クラスは container.add(button, BorderLayout.CENTER) のような書き方をします。また、GridBagLayout クラスは GridBagConstraints クラス、SpingLayout クラスであれば内部クラスの SpringLayout.Constraints クラスを一緒に使用します。 簡単にいえば LayoutManager2 インタフェースは constraints を使用して、単に並べるだけでなくもう少し賢く並べられるようにしようというものです。 さて、決定版たるレイアウトマネージャを作るとしたら、どちらを選びますか? もちろん LayoutManger2 インタフェースです。LayoutManager2 インタフェースでレイアウトマネージャが作れれば、LayoutManager インタフェースでレイアウトマネージャを作ることも簡単です。 それでは次章から具体的にレイアウトマネージャを作っていきましょう。
|
どんなレイアウトマネージャを作ろうか |
||
作るといっても、どんなものを作るかをまず決めなくてはいけません。 私がやりたかったことは
こんな感じです。 GridBagLayout クラスではコンポーネントによってグリッドの大きさが変化してしまうことが、使い方を難しくしています。それならば、グリッドは一定にしてしまおうと思ったのです。 GridLayout という名前は使われてしまっているので、グリッドの代わりに格子 Lattice という言葉を使って LatticeLayout という名前にしましょう。
|
レイアウトマネージャの動作 |
||||||||
LayoutManager2 インタフェースでは次に示すメソッドをインプリメントしなくてはいけません。
また、親インタフェースの LayoutManager インタフェースでは次のメソッドをインプリメントします。
全部を 0 から実装していたら大変なので、ある程度は他の LayoutManager から Cut & Paste してしまいましょう。 さて、これらのメソッドはどのようにコールされているのでしょう。簡単なプログラムを作って試してみました。GridBagLayout クラスを派生させたクラスをつくり、LayoutManager/LayoutManager2 で定義されたメソッドをオーバロードします。処理自体は親クラスに委譲して、コールされたことを出力するだけのプログラムです。
これを使って次のテストプログラムを動作させて見ましょう。ボタン 3 つを横に並べただけのプログラムです。
実行するとボタンが 3 つ並んだウィンドウが描画されるはずです。コマンドプロンプトには次のように出力されました。
これから分かることは、
また、類推できることは
XXXXLayoutSize というメソッドはコンテナが自分のサイズを決めるときに使用されます。例えば、JScrollPane に JPanel が貼られるときに、JPanel の大きさが分からなければ、JScrollPane もスクロールバーを出していいのかどうか分かりません。 かといって JPanel は自分にどのようなコンポーネントがはられているか、また自分のレイアウトマネージャによってその大きさは変わってしまいます。このような情報は誰がもっているかというとレイアウトマネージャが持っているので、サイズを決めるときにもレイアウトマネージャに問い合わせするのです。
|
Constraints |
|||
レイアウトマネージャのだいたいの動作が分かったところで、さっそくレイアウトマネージャを作っていきたいところですが、その前に Constraints を作ってしまいましょう。 LayoutManger2 インタフェースで使われるのが Constraints です。Constraints には BorderLayout の BorderLayout.CENTER, BorderLayout.SOUTH のように簡単な Constraints から、GridBagConstraints クラスや SprintLayout.Constraints クラスのようにいろいろとプロパティを持つものまで各種あります。 LatticeLayout クラスでも GridBagConstraints のように独立したクラスで Constraints を作っていきます。 Constraints にはレイアウトするときの条件を記述できるようにします。前述したような特徴を持つとしたらどんな条件が必要でしょうか。
結構ありました。これだけあれば十分でしょう。後はこれをクラスにして、指定を行いやすいように定数を定義します。 また、なにも指定しないときのデフォルト値もちゃんと決めておきましょう。 忘れていけないのは、コンストラクタにコピーコンストラクタか clone メソッドを用意することです。GridBagConstraints などの使い方を見てみると、プロパティは public になっておりコンポーネントごとに値を直接変えています。したがって、setConstraints メソッドがコールされたときに、Constraints をそのまま使ってしまっては値が変わってしまうので、必ずコピーをとるようにします。その時に使われるのが、コピーコンストラクタか clone メソッドです。 というわけでこんな風になりました。分量はありますが、中身はたいしたことないはずです。
|
雑魚を片づけてしまう |
||||||||
さて、本題のレイアウトマネージャに移りましょう。 レイアウトマネージャの中で一番重要なのが、layoutContainer メソッドであることは動作を考えればすぐ分かると思います。これは後回しにして、そのほかのメソッドを片付けてしまいましょう。 まずはプロパティ。 プロパティとしては格子のサイズを決めなくてはいけません。縦と横です。 もう一つ、レイアウトするコンポーネントを保持するためのコンテナが必要です。ここではコンポーネントと LatticeConstraints をペアで持つ必要があるので、Map を使用しました。 コンストラクタではこれらのプロパティの初期設定を行っています。
次にたいしたことのないメソッドです。まず getLayoutAlignmntX/getLayoutAlignmentY メソッドです。このメソッドはどういう時使われるのかいまいちよく分かりません。そのほかのレイアウトマネージャを見てみると 0.5 を返しているだけのようなので、ここでもそうしました。
次に invalidLayout メソッドです。レイアウトを無効にするときに呼ばれるのですが、特に何も行いません。
さて、だんだん核心に近づいてきました。次はコンポーネントの add/remove 関連のメソッドです。addLayoutComponent メソッドと removeLayoutComponent メソッドですが、それ以外に GridBagLayout クラスでも使われている setConstraints メソッドも作成しました。 addLayoutComponent(String name, Component comp) メソッドは今は使われていないメソッドなので、定義だけです。 もう 1 つの addLayoutComponent メソッドはコンポーネントと constraints がペアになっているので、それを compTable に格納します。もし、constraints が LatticeConstraints オブジェクトでなければ IllegalArgumentException を発生させています。 setConstraints メソッドは単純に compTable にコンポーネントと constraints を格納するだけです。
setConstratins メソッドがあると、getConstraints メソッドも欲しくなります。 getConstraints メソッドは内部的に lookupConstratins メソッドをコールします。lookupConstraints メソッドは compTable から目当ての LatticeConstraints オブジェクトを取り出します。 もし、constraints が null だったらデフォルト値の入った LatticeConstraints オブジェクトをセットしておきます。 lookupConstraints メソッドは内部で使われることを前提にしていますが、getConstraints メソッドは外部からコールされれます。そのために、constraints の複製を返すようにしています。 もし、constraints を複製しなければ、勝手に constraints のプロパティが書き換えられてしまい、レイアウトがおかしくなってしまいかねません。
残るは preferredLayoutSize/maximumLayoutSize/minimumLayoutSize メソッドです。 これらは Container オブジェクトの preferredSize/maximumSize/minimumSize を決めるために使われるのですが、どのようにして決めるようにしましょう ? とても単純ですが、次のようにしたいと思います。
ようするに一番大きなコンポーネントのサイズを格子 1 枡分としてしまっているのです。preferred/maximum/minimum の違いはありますが、行う処理は同じです。 結局こうなりました。
|
レイアウト |
||||||
やっと核心のレイアウトまできました。 レイアウトを行うというのはどういうことなのでしょう。簡単にいえば、貼られているコンポーネントに対し Component#setBounds メソッドで位置と大きさを設定するということです。 その第 1 歩としてコンテナの大きさを取得して、格子の大きさを決めてしまいましょう。コンテナの大きさは getSize で取れますが、忘れていけないのが余白分です。これは getInsets で取得できます。
そして、貼られているコンポーネントを取り出して、その位置と大きさを決めていきます。 コンポーネントを取り出すのは getComponents メソッドです。それをループで 1 つ 1 つ処理していきます。 位置の x と y は constraints で設定された値から求めることができます。 問題は大きさの方です。コンポーネントには preferredSize と、constraints で設定した大きさの 2 種類があります。この大きさによってコンポーネントを拡大・縮小したり、配置を考えたりしなくてはいけません。
まず、横軸方向から考えましょう。 設定された幅の方が preferredSize より大きい場合、拡大するか、右詰にするか、中央に配置するか、左詰にします。 fill が NONE と VERTICAL の時は拡大しないので、配置を考えます。halign が LEFT だったらそのままでいいので何もしません。CENTER と RIGHT の時は x を再設定します。コンポーネントの大きさは preferredSize のままにします。 fill がその他の場合は拡大しますが、その大きさは w のままでいいのでコードには記述しなくても大丈夫です。 w が prefW より小さい場合縮小するか、そのままにするかのどちらかです。adjust の値が NONE と VERTICAL の時はそのままなので w を prefW にします。縮小は w のままなので、記述しません。
縦方向も横方向とほぼ同じなので省略します。 最後に大きさや位置がおかしくなっていないかを調べておきましょう。大丈夫なら位置 x, y で大きさ w, h でコンポーネントを設定します。
これでできあがりです。
|
使ってみよう |
|||||||
せっかくできたので、サンプルを作ってみました。 アプレットとしてもアプリケーションとしても使用できます。 LatticeConstraints オブジェクトに適当な値を設定して、LatticeLayout.setConstraints メソッドを使ってコンポーネントと結び付けて起きます。その後、コンテナに add すれば OK。
|
おわりに |
||||||||
どうでしょう。私は使いやすいと思うんだけど、どうですか。すくなくとも GridBagLayout よりは分かりやすいと思います。 こんな風に結構簡単にレイアウトマネージャは作れるので、もしコアで提供されているものでどうしても満足できなければ作ってしまうのもいいのではないでしょうか。
今回作成したソース
(Sep. 2003) |
|