さてさて、GridBagLayoutにもっと手を加えて、実用に耐えるキレイなレイアウトを作りませう。ポイントはGridBagLayout本体よりも、一つひとつの部品の制約条件を決めるGirdBagConstraintsだ。
今まで出てきたGridBagConstraintsのプロパティは次の4つ。
gridx.....X軸方向の位置(0から始まる)
gridy.....Y軸方向の位置(0から始まる)
gridwidth..X軸方向の幅広さ
gridheight..Y軸方向の高さ
GridBagLayoutはその名前のとおり、grid(碁盤の目)を基準にしているから、碁盤の目のどの位置から始まって、碁盤の目で測ってどれくらいの大きさかを指定すれば、いちおうそれらしく表示される。gridxとgridyが部品の左上の位置で、gridwidthとgridheightが、その左上の位置からどれくらいの広がりがあるかを示す。ここまでが前回の話。
次は、GridBagLayoutのgridじゃなくって、bag(袋)の部分がポイント。bagのない普通のGridLayoutは、決められたひとつのマス目のいっぱいいっぱいに部品がベタッと広がって表示されてしまう。けれどbagのついたGridBagLayoutは、一つひとつの袋の中で、部品をどんな風に表示するかを細かく調整できる。そこが最大のメリット!
まず前回の例で、左半分にあるlist1は、全体のウィンドウの枠までキチキチに広がってカッコ悪い。そこで、list1のまわりに4ドットだけ余白を作ろう。そのとき使うGridBagConstraintsの機能がinsets。このinsetsプロパティーには新たな部品Insetsを使う。たとえば上下左右に4ドットずつの余白を作る場合、
...のように、list1の制約条件を決める部分へinsetsの設定を追加すればいい。Insetsも他の部品と同じくnewで作るだけ。かっこの中身は、(上余白、左余白、下余白、右余白)という具合に、反時計周りに余白の幅を設定する。これでちょっとだけ余白ができて見ばえがよくなる。
次は「氏名」「郵便番号」などのlabel1からlabel4までの4つのJLabel。中央あわせでならんでしまっていた。これではみっともないので、右ぞろえに変えよう。そのときに使うのがGridBagConstraintsのanchorという機能。このプロパティは、「袋」よりもその中に入れる部品が小さいとき、部品を「袋」のどの方向に寄せるかを指定できる。今の場合なら、右ぞろえにしたいので、定数GridBagConstraints.EASTをanchorに代入する。
これもlabel1の制約条件にanchorの設定を1行追加するだけ。ここで注意したいのは、今、GridBagConstraints型の変数cを使いまわししているということ。だからlabel1の制約条件の設定のときも、さっきlist1で設定したinsetsの条件が、クリアされずにまだ生きている。もしクリアしたいならこのlabel1の個所で
という具合に、余白を上下左右ともゼロにもどしておかなきゃいけない。でも今は、他の部品のまわりにも余白を作る意味で、わざとこのinsetsの設定を残しておくことにしよう。
ということは、label2〜label4まで、残り3つのJLabelの設定では、anchorを設定しなくても、label1で設定したanchorの使いまわしがきくってこと。これがリサイクルの便利なところ。だからGridBagConstraints型の変数は、一つだけ作って、すべての部品に使いまわすほうが効率が良いね。
さて、次はtext1〜text4まで、4つの文字入力欄 JTextField。これは全部左ぞろえにしたい。だから同じanchorを使って、今度は定数GridBagConstraints.WESTを設定する。今度も使いまわしがきくから、最初のtext1のときに設定するだけでいい。
こうしてanchorを上書きしてしまえば、label4までで設定したGridBagConstraints.EASTはもちろん無効になって、WESTに変わる。これでJLabelはすべて右ぞろえ、文字入力欄はすべて左ぞろえで、ずいぶん見ばえがよくなった。
それぞれの部品の周囲に4ドットずつ余白があるから見やすくなった。実は文字入力欄の最後、text4で設定したanchorが、ボタンにも効いているから、「登録」「終了」ボタンが左ぞろえになっちゃてるんだよね。
これはこれでいいんだけど、やっぱりボタンは右へ詰めておこう。つまり、ボタンの制約条件の設定のところで、anchorにGridBagConstraints.EASTを設定すればいいわけ。そうすると、下図のようになる。
なぬ!?何なんだこの「登録」と「終了」の不自然な間(ま)は?実はGridBagLayoutは左の部品から順番に「袋」の大きさを計算するんだけど、そのとき「袋」の大きさを部品に合わせちゃう。まず「登録」ボタンの大きさに合わせて「袋」の大きさを決めて、残った余白をぜ〜んぶ「終了」ボタンに割り当てちゃうんだ。だからこういう不自然な間隔があいてしまう。
こんなとき、自分で列の幅を自由に変えられたらいいのに...って思うよね。「登録」ボタン用の幅をグッと広めにとって右づめにして、「終了」ボタン用の幅をせまくすれば...。大丈夫、そんなときにはGridBagConstraintsのweightxを使おう。これは文字どおりX軸方向の重み(weight)を決める機能だ。
ここでは「登録」ボタンの制約条件で、X軸方向の横幅をグッと1.5列分まで広げることにする。すると、もともと2つのボタンで2列分を占領してたから、2.0−1.5=0.5で、「終了」ボタンは0.5列分になるよね。だから「終了」ボタンの制約条件ではweightxに0.5を設定すればいい。
button1の方ではすでに右ぞろえのためのGridBagConstraints.EASTは設定してあるから注意してね。こんな風にweightxの値に、何列分かという値を代入してやると、列の重み(weight)を変えることができる。こりゃ便利だねぇ。この結果が下図。
これでかなりイメージに近くなってきたよね。もう分かるとおもうけど、Y軸方向の重みを変えたいときは、weightyというプロパティを同じように設定すればいい。
でもまだ2つのボタンの間が気になる。この例ではボタンの中に2文字しかないので、ボタンが文字の幅に合わせて小さめになっている。だからJButtonにsetPreferredSize()で既定値として大きさを設定してしまえばOK。これはGridBagLayoutとは関係ないけど、ワンポイント・アドバイスね。その結果は下図。
これでまさに最初のイメージどおりだね。前回の初めにGridBagLayoutを使わずに作った例と見比べてみてほしい。全然ちがうよね。これがGridBagLayoutの威力なわけだ。GridBagLayoutの使い方を一度おぼえてしまうと、他のレイアウトマネージャーを使う気がしなくなるよ、まったく。
(GridBagConstraintsでは、他にfillという機能もある。これは自分でJava APIを調べて試してみよう。「袋」よりも部品が小さいとき、横幅いっぱいに表示させたり、タテいっぱいに表示させたり、タテ・ヨコ両方ともいっぱいに表示させたりする機能だよ)
c.gridx = 0; c.gridy = 0; c.gridwidth = 2; c.gridheight = 6; c.insets = new Insets(4, 4, 4, 4); gb.setConstraints(list1, c); getContentPane().add(list1);
c.gridx = 2; c.gridy = 0; c.gridwidth = 1; c.gridheight = 1; c.anchor = GridBagConstraints.EAST; gb.setConstraints(label1, c); getContentPane().add(label1);
c.gridx = 2; c.gridy = 0; c.gridwidth = 1; c.gridheight = 1; c.insets = new Insets(0, 0, 0, 0); c.anchor = GridBagConstraints.EAST; gb.setConstraints(label1, c); getContentPane().add(label1);
c.gridx = 3; c.gridy = 0; c.gridwidth = 2; c.gridheight = 1; c.anchor = GridBagConstraints.WEST; gb.setConstraints(text1, c); getContentPane().add(text1);
c.gridx = 3; c.gridy = 5; c.gridwidth = 1; c.gridheight = 1; c.anchor = GridBagConstraints.EAST; c.weightx = 1.5; gb.setConstraints(button1, c); getContentPane().add(button1); c.gridx = 4; c.gridy = 5; c.gridwidth = 1; c.gridheight = 1; c.weightx = 0.5; gb.setConstraints(button2, c); getContentPane().add(button2);