ウィンドウ

典型的なGUIアプリケーションに特徴として、ウィンドウが存在するという点が挙げられます。 FLTKにもやはりウィンドウが存在しています。 すでにHello Worldやその他のサンプルアプリケーションでウィンドウを表示するプログラムを書いてきました。

ウィンドウはウィジェットの一種である

FLTKにおいて、ウィンドウもウィジェットです。 すなわち、Fl_Widgetクラスをルートとするクラス階層の間に存在しています1)

ウィンドウとアプリケーション

ウィンドウはその他のウィジェットよりもアプリケーションにとって特別な役割を持ちます。 Hello Worldアプリケーションのコードからそのことを探っていきます。

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>

int main(int argc, char **argv) {
  // 親ウィジェット(メインウィンドウ)
  Fl_Window *window = new Fl_Window(340, 180);

  // 子ウィジェット
  Fl_Box *box = new Fl_Box(20, 40, 300, 100, "Hello, World!");
  box->box(FL_UP_BOX);
  box->labelfont(FL_BOLD + FL_ITALIC);
  box->labelsize(36);
  box->labeltype(FL_SHADOW_LABEL);
  
  window->end();
  window->show(argc, argv);
  return Fl::run();
}

このプログラムを実行すると、次のような動作が観察できます。

  1. 画面にウィンドウが表示される。
  2. ウィンドウはタイトルバーやボーダーで装飾されている。
  3. ウィンドウの中に、作成した子ウィジェットが配置される。
  4. ウィンドウを閉じるとプログラムが終了する。逆に言えば、ウィンドウが閉じられるまでプログラムは終了しない。

ウィンドウがアプリケーションにとって特別であると考えられるのは、この中で4つ目の点においてです。

実験1

いくつかコードに変更を加えて変化を観察してみます。

  • window→show(argc, argv);の行を削除すると、ウィンドウは表示されなくなり、すぐにプログラムは終了してしまう
  • return Fl::run();の行を削除すると、やはりウィンドウは表示さなくなり、すぐにプログラムは終了してしまう

こういった変化が見られることから、次のように導かれます。

  • window→show()が呼び出されなければ、ウィンドウは表示されない。
  • ウィンドウが表示されている間、Fl::run()の呼び出しが完了することはない。
    • 逆に言えば、ウィンドウが閉じられたらFl::run()の呼び出しは完了してmain関数に戻ってくる。そしてプログラムは終了する。
  • Fl::run()が呼び出されなければ、ウィンドウが閉じられるのを待つことなくプログラムは終了する。

実験2

ウィンドウがアプリケーションにとって特別であることに確信を得るために、もう一つ実験をしてみます。

int main(int argc, char **argv) {
  // Fl_Windowクラスのオブジェクト(メインウィンドウ)を作成しない。

  // このFl_Boxクラスのオブジェクトがルートのウィジェットになるはず。
  // ルートがFl_Windowでなければどうなるか?
  Fl_Box *box = new Fl_Box(20, 40, 300, 100, "Hello, World!");
  box->box(FL_UP_BOX);
  box->labelfont(FL_BOLD + FL_ITALIC);
  box->labelsize(36);
  box->labeltype(FL_SHADOW_LABEL);
  
  box->show(); // メンバ関数show()はFl_Boxにも存在する。
  
  return Fl::run();
}

Fl_Windowクラスに関連するコードをすべて取り除きました。 このプログラムを実行したときどうなるかというと、プログラムはすぐに終了してしまいます。 Fl_BoxクラスがFl_Windowクラスの代わりにルートウィンドウのような役割を果たすことはありません。

これでウィンドウがアプリケーションにとって特別なウィジェットであることがはっきりとしました。 コード上には表れていませんが、FLTKは水面下でウィンドウが存在していることを認識しています。 Fl::run()は、少なくとも1つのウィンドウが存在する(クローズされていない)限り、何らかのループ状態に入ってアプリケーションの状態と進行を管理していると考えられます。 まだあくまで推測に過ぎませんが、今の段階ではこのくらいの理解でもなんとかなるでしょう。

begin()とend()

Hello Worldのコードは直感的ではあるものの、いくつか奇妙な、もしくは気持ち悪い点があります。 それは、子ウィジェットであるFl_Boxクラスのオブジェクトを、親ウィジェットであるFl_Windowクラスのオブジェクトに追加するコードをどこにも書いていないところです。 例えば、もしwindow→add_child(box)といった風なコードがあるならば、この気持ち悪さは解消されるでしょう。 FLTKはこのような明示的に子ウィジェットを追加していくようなスタイルにはなっていません。 FLTKが採用しているのは、parent.begin()からparent.end()の間に作成されたウィジェットは、自動的にparentの子になるというスタイルです。

今まで見てきたHello Worldに気持ち悪さがあったのは、end()が書かれているのにbegin()が書かれていないことです。 なぜ書かれていないのかというと、理由は簡単です。 begin()は実際にメンバ関数として存在します。 そして、Fl_Windowクラスのコンストラクタ内で2)begin()が呼び出されています。 begin()を繰り返し呼び出しても害はないので、Hello Worldは次のように書き換えられます。

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>

int main(int argc, char **argv) {
  Fl_Window *window = new Fl_Window(340, 180);
  
  // 明示的にbegin()を呼び出しても害はない。ただし冗長なので通常は省略する。
  window->begin();
      // begin()からend()の間で作成されたウィジェットはwindowの子になる。
      Fl_Box *box = new Fl_Box(20, 40, 300, 100, "Hello, World!");
      box->box(FL_UP_BOX);
      box->labelfont(FL_BOLD + FL_ITALIC);
      box->labelsize(36);
      box->labeltype(FL_SHADOW_LABEL);
  window->end();
  // これ以降に作成されたウィジェットはwindowの子にはならない。結果、表示されない。
  
  window->show(argc, argv);
  return Fl::run();
}

これで気持ち悪さはだいぶ解消されるかと思います。 ただし、Fl_Windowクラスのオブジェクトを作成した時点で自動的にコンストラクタ内でbegin()が呼ばれることを知っているならば、明示的にbegin()を呼び出すのは冗長なだけです。 省略することに慣れてしまった方が良いでしょう。

Fl_WindowクラスはFl_Groupクラスから派生している

begin()とend()はFl_Windowクラスそのもののメンバ関数ではありません。 Fl_Groupクラスのパブリックメンバ関数です。 Fl_Groupクラスもやはりウィジェットです。 つまり、Fl_Widgetクラスから派生しています。 Fl_Groupクラスを継承する多くの派生クラスが存在しています。 Fl_Windowクラスはその一つです。

Fl_Windowクラスの前後のクラス階層は次のようになっています。

Fl_Widget
   |
   +----Fl_Group
           |
           +----Fl_Window
                   |
                   +----Fl_Double_Window, Fl_Gl_Window,
                        Fl_Overlay_Window, Fl_Single_Window
	  

Fl_Groupクラスは抽象クラスではないので、そのインスタンスオブジェクトを作成することは可能で、ウィジェットをグループ化するためのウィジェットとして用いることができます。 同時に、継承されることによってウィジェットのコンテナとしての機能を派生クラスに提供する役目もになっています。

ひとつ気をつけたいおきたいことがあります。 Fl_Groupクラスから派生するウィジェットのクラスはそこそこの数がありますが、そうでないものも多くあります。 つまり、どのウィジェットでも子ウィジェットを持つことができるわけではありません。 例えば、Fl_BoxクラスはFl_Groupクラスから派生していません。 したがって、Fl_Boxクラスのオブジェクトは子ウィジェットを持つことができません。

Fl_Box *box = new Fl_Box(...);
Fl_Box *non_child_box = new Fl_Box(...);

このコードで、non_child_boxはboxの子にはなりません。

一方で、Fl_WindowクラスはFl_Groupクラスから派生しているので、子ウィジェットを持つことができます。 この能力はFl_Windowクラスそのものに備わっているのではなくFl_Groupクラスが与えてくれたものだと理解しておくと、FLTKのウィジェットの仕組みに親しみが持てるようになるかもしれません。

参考

1)
IS-A関係とも言います。
2)
正確にはベースクラスのFl_Groupクラスのコンストラクタ内です。
文書の編集
文書の先頭へ