GUIとレイアウトの基本
こないだ会社で軽く説明したかんじ、意外と需要がありそうだったのでまとめておく。
ツールプログラミングの第一歩。理論編。
基本のクラス構造
詳細設計はフレームワークによって様々だし、実際にはもっと複雑だけど、ざっくりまとめるとだいたいこんなかんじ。
EventDispatcher
名前の通り「イベントを Dispatch(送り出す)人」で、これがないと始まらない。
UI 系はユーザーからの入力を如何に受け取るかがとても大事で、「ユーザー入力」は「Event」というかたちに抽象化されて、モジュール間を行き来することになる。
たとえば「ウィンドウ内に配置されているボタンがクリックされた」ときは、だいたいこんなかんじの挙動になる。*1
- ウィンドウが Clicked イベントを発行する。
- クリックされた位置がボタンの描画範囲内であれば、ボタンが Clicked イベントを発行する。
とりあえずここでは、「ユーザー入力が Event というかたちに抽象化される」「モジュール間で Event の受け渡しが起きる」ということだけ理解できれば問題なし。
Widget
「ボタン」や「チェックボックス」「テキストラベル」といった実際の UI 部品のことを Widget と呼ぶ。*2
実際にユーザー入力を受け取ったり、ユーザーに何らかの情報を提示するために使われる。必然的に、実際に画面上に描画されてユーザーが視覚的に認知できる要素であることが多い。*3
自動レイアウトの基本形
以下にあるレイアウトは大抵のフレームワークで提供されているので、暗記しておくと良い。*8
Stack 系
子供を縦横一直線に並べるためのレイアウト。*9
並べる方向によって Row 系と Column 系に分かれる。
Row (Horizontal)
横向きに並べるためのレイアウト。RowLayout, HorizontalStack とも呼ばれる。
Column (Vertical)
縦向きに並べるためのレイアウト。ColumnLayout, VerticalStack とも呼ばれる。
Grid 系
子供を格子状に並べるためのレイアウト。縦横に配置される子供の数をどう決めるかによって Fixed 系と Flow 系に分かれる。
Fixed
縦横に並べる個数をあらかじめ決めておく格子状レイアウト。
レイアウト自身がリサイズされても、縦横に並ぶ個数は変わらない。
ちなみに、Stack 系を組み合わせることでも似たようなことが実現できる。行や列ごとに「並べる子供の数」や「子供の大きさ」を変えたいときは、こちらを使うほうが制御しやすいことが多い。
Flow
縦横のサイズに応じて並べる個数を動的に変える格子状レイアウト。*10
レイアウト自身がリサイズされると並ぶ個数も変わるので、いくつ並べればいいか分からないときに便利。
ScrollWidget
Layout ではないけど覚えておきたいものとして、ScrollWidget がある。*11
Widget や Layout をこれの子供にすると、子供の描画範囲が自分の描画範囲よりも広いときにスクロールできるようになる。*12
レイアウトシステムを使うときの必然として、「レイアウト後の配置」が実行するまで確定しない。つまり、各 GUI 要素があらかじめ用意していた範囲よりも大きく描画されてしまう*13ことがある。そういった場合が想定されるときは、あらかじめ Scroll を仕込んでおくと良い。*14
親子間での Event Routing
最後に、少しだけ小難しい話題として Event Routing *15を取り上げておく。
この図にあるように、GUI というのは階層構造として組まれることになる。
まず OS がユーザー入力を受け取り、それを各アプリのメインウィンドウに通知する。通知を受け取ったメインウィンドウ*16は、それを自分の子供に伝える。そして、子供を持たない UI 要素*17まで通知が届いたら、今度はそれを親に返す。このとき、イベントが子供に伝わる処理を tunneing/captureing/previewing、親に戻す処理を bubbling と呼ぶ。
基本的には、bubbling だけを意識しておけば良い。*18
例えば「子供の UI 要素が無効化されている」場合、レイアウトシステムがそれを検知して、処理負荷軽減のためにイベントの tunneling を中断したりする。
また、何らかの理由で「それぞれ異なる階層にある UI 部品同士を連携させたい」場合や、「イベントの通知を偽装したい」場合などに、tunneing や bubbling の片方または両方をフックしてやることがある。
ただ、Event Routing のフック処理なんてものは例外的な処理だと考えるほうが無難だと思う。特殊処理であることを明確に意識して実装するなら構わないが、基本的にはまず、プログラム設計のほうを見直すことをおすすめしたい。
*1:実際には、まず最初にマウス処理を請け負うドライバソフトウェアが Clicked を発行して、OS がそれを受け取る。そのクリック位置がウィンドウの描画範囲内だったときに、OS のウィンドウ管理システムが、ウィンドウにイベント処理を移管する。
*2:フレームワークによっては Control と呼ばれることもある。
*3:「状況に応じて表示/非表示を切り替えられる Widget」というのも存在するが、なんにせよ「表示可能」というのがひとつの大切な要素ではある。
*4:フレームワークによっては Panel や Block と呼ばれることもある。
*5:ある程度の規模の GUI だと総 Widget 数が 4 桁以上になることも決して珍しくはないので、実際のところ自動配置がないとやってられない場合が多い。
*6:というか、ひとつの Layout だけで完結するケースはあまり多くない。大抵の GUI で再帰的に配置されている。
*7:一般的な名前があるのかはよく分からない。他にも ItemsControl や ItemView といった呼び名がある。
*8:というか、世の中にある自動レイアウトの大半は、これらの亜種や、これらを組み合わせたものだったりする。
*11:フレームワークによっては ScrollView とも呼ばれる。
*12:WidgetではなくLayoutにみえるかもしれないが、これ自体はScrollBarという「ユーザー入力の受け取り手」の存在を前提としていることもあり、基本的にはWidgetに分類される。
*14:実際、List 系の Widget にはあらかじめ Scroll が仕込まれていることも多い。
*15:フレームワークによっては Event Propagation とも呼ばれる。
*16:ルートノード
*17:リーフノード