しかしながら位置座標をプログラマが直接与える方法は、面倒で間違いも多くなる。 ほとんどのプログラムの場合、GUI 部品の配置は、左揃えでたてに並べるだけ、あるいは格子状に上下左右を揃えて並べるなど、比較的単純なものがほとんどである。
そこでプログラマからは GUI 部品のおおまかな相対位置だけを指定するようにして、最終的な配置は toolkit が自動的に計算する方法が望ましい。 多くの toolkit では、GUI 部品の自動配置のために、レイアウト・マネージャあるいはジオメトリ・マネージャと呼ばれる GUI 部品を用意している。
レイアウト・マネージャは Java 言語の AWT のようにプラットフォームに依存しない GUI を提供しようとしている toolkit ではとくに重要である。 画面の大きさや利用可能なフォントの種類は、プラットフォームによってまちまちである。 GUI 部品の位置座標をプログラマが直接与えてしまうと、特定のプラットフォームでは美しく配置されても、別なプラットフォームでは望みの配置が得られないかもしれない。 これを避けるためには、レイアウト・マネージャがそのプラットフォームの性質にあわせて、適切に GUI 部品を配置することが大切である。
レイアウト・マネージャは、いくつかの GUI 部品を子供として内部に包含する GUI 部品である。 子供たちの位置はレイアウト・マネージャによって計算される。 レイアウト・マネージャのクラスを Layout としよう。 (完全なクラス定義)
Xwindow オブジェクトの側から見ると、Layout オブジェクトとその子オブジェクトは全体として 1 個の GUI 部品として認識される。
各種のイベントも Xwindow オブジェクトからは、親である Layout オブジェクトに対してだけ渡される。 Layout オブジェクトは渡されたイベントを、適宜、子オブジェクトにも配送しなければならない。 次のメソッドは再描画イベントを処理するものである。
void Layout::redraw() { for (int i = 0; i < num; ++i) children[i]->redraw(); }
ボタンの押し下げイベントも、マウスがどの子オブジェクトの上に位置しているかを調べ、その子オブジェクトに渡さなければならない。
void Layout::buttonDown(int x, int y) { for (int i = 0; i < num; ++i) if (children[i]->inside(x, y)) { clicked = children[i]; clicked->buttonDown(x, y); break; } }
イベントが渡される先のオブジェクトは clicked に記録される。 これはボタンの押し上げイベントを渡す先を決めるに使われる。
void Layout::buttonUp(int x, int y) { if (clicked != NULL) clicked->buttonUp(x, y); }
この処理は Xwindow の mainloop() 中でのボタンの押し上げイベントの処理と同等であることに注意されたい。 Layout オブジェクトは、Xwindow オブジェクトが表すウィンドウの中に含まれる小さなウィンドウを表し、全体で入れ子構造を作っている。
例として示す Layout オブジェクトは、子オブジェクトを左揃いになるように上から順に並べるものである。 relocate() の定義は次のようである。
void Layout::relocate() { int y = y0; int maxw = 0; for (int i = 0; i < num; ++i) { GuiObject* child = children[i]; child->move(x0, y); y += child->getHeight() + 5; int w = child->getWidth(); if (w > maxw) maxw = w; } width = maxw; height = y - 5; }
Layout オブジェクト自体の位置は (x0, y0) である。 この値をもとに子オブジェクトの位置を計算し、move() メソッドでその位置に子オブジェクトを移動している。 さらに子オブジェクトの新しい位置をもとに、Layout オブジェクトが全ての子オブジェクトを包含するように、Layout オブジェクト自体の高さと幅を再計算している。
上で示した再配置のアルゴリズムは非常に単純なものだが、Layout のサブクラスを作り、relocate() を上書きして、さまざまなアルゴリズムを実装することができる。
例えば add() メソッドを拡張して、プログラマが配置のためのヒントを与えられるようにすると、より高度な配置も可能である。 例えば add() の第 2 引数として、左よせ、センタリング、右よせ、を指定できるようにすることが考えられる。
Layout* manager = new Layout(...); manager->add(textbox1, CENTER);
また Layout 自体も GuiObject のサブクラスなので、Layout オブジェクトの子オブジェクトとして別な Layout オブジェクトを与えることも可能である。 この技法を使うと、子オブジェクトを単純に縦に並べるレイアウト・マネージャと 横に並べるレイアウト・マネージャを組み合わせて、GUI 部品を格子状に並べることも可能である。
なお Layout オブジェクトが入れ子になった場合、親の Layout オブジェクトが子供の Layout オブジェクトの位置を変更する可能性がある。 その場合、子供の Layout オブジェクトは GUI 部品の再配置をしなければならない。 これを処理するため、Layout クラスの move() メソッドは relocate() を呼ぶように上書きされている。
void Layout::move(int new_x, int new_y) { GuiObject::move(new_x, new_y); relocate(); }
参考: subtract-layout.cc (コンパイルするには make subtract-layout とする)
Xwindow オブジェクトは、Layout オブジェクトをひとつだけ含み、受け取ったイベントは無条件にその Layout オブジェクトに渡すようにすればよい。 これまで Xwindow オブジェクトに add() されていた GUI 部品は、その Layout オブジェクトに add() されることになる。 これまでの toolkit の設計と互換性をとなるなら、Xwindow オブジェクトに含まれる Layout オブジェクトは、子供の自動配置をおこなわないようにするのがよいだろう。
Copyright (C) 1999-2000 Shigeru Chiba
Email: chiba@is.tsukuba.ac.jp