こんにちは、高木です。
今回はTcl/TkのGUI部品にあたるウィジェットをC++から使う方法を考えていくことにします。といっても、一足飛びに高度なことはできませんので、最初は簡単なところから始めて、少しずつ改善していこうと考えています。
まずは基本方針を決めるところから始めましょう。ウィジェットを生成するには、次の形式のコマンドを実行することになります。
0 1 2 |
ウィジェットの種類 パス名 [オプション...] |
ここで、ウィジェットの種類はlabelとかbuttonなどを指定します。パス名というのは個々のウィジェットを特定する文字列で、ピリオドで区切った階層を表しています。たとえば、メインウィンドウ「.」が持つフレーム「frame1」の中に配置されたラベル「label1」であれば、「.frame1.label1」がパス名になります。
オプションというのはハイフンで始まる文字列で、多くの場合引数を取ります。たとえば、これまでにも出てきたボタンを生成するコマンドを見てみましょう。
0 1 2 |
button .b -text exit -command exit |
このコマンドでは、buttonがウィジェットの種類、.bがパス名にあたります。そして、-text exitと-command exitがオプションです。-text exitというのはウィジェットに表示する文字列を指定するオプションで、多くのウィジェットで使用します。exitが引数に相当すると考えてください。-command exitはウィジェットを操作したときに実行するコマンドを登録するためのオプションです。
TkのC言語APIでは、ウィジェットはTk_Window型を使って特定します。効率を考えればC言語APIを積極的に使った方がいいのでしょうが、扱いがちょっと大変ですし、将来のバージョン(Tcl/Tk 9.x?)での互換性も気になります。また、Tk_Window型を使う場合はTcl_Preserve関数とTcl_Release関数を使ってインタープリターのように生存期間を自分で管理する必要も出てきます。これはちょっと面倒です。
とりあえずは、Tcl_Obj型でコマンドに並べる文字列を管理して、interpreter::evaluateメンバー関数で評価する方が無難ではないかと考えています。
というわけで、ウィジェットをラップするクラスはこんな感じにしてみようかと思います。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
namespace tk { class widget { public: // メインウィンドウをラップするオブジェクトの構築 widget(tcl::interpreter interp) : interp_(interp), path_(u8".") { } // 一般のウィジェットを構築 widget(const tcl::obj& type, const widget& parent, const tcl::obj& name, std::initializer_list<tcl::obj> options) : interp_(parent.interp_) { if (parent.path_.get_u8string_view() == u8".") { Tcl_Obj* objv[] = { name.get() }; this->path_ = Tcl_Format(this->interp_.get(), ".%s", 1, objv); } else { Tcl_Obj* objv[] = { parent.path_.get(), name.get() }; this->path_ = Tcl_Format(this->interp_.get(), "%s.%s", 2, objv); } // コマンドの組み立て std::vector<Tcl_Obj*> args{ type.get(), this->path_.get() }; std::for_each(options.begin(), options.end(), [&args](auto option) { args.push_back(option.get()); }); args.push_back(nullptr); auto command = Tcl_ConcatObj(args.size() - 1, args.data()); this->interp_.evaluate(command); } private: tcl::interpreter interp_; tcl::obj path_; }; } |
本当に最低限しかありませんが、これでウィジェットを構築することができるようになります。使い方はこんな感じです。
0 1 2 3 |
tk::widget b(u8"button", tk::widget(interp), u8"b", { u8"-text test", u8"-command test" }); interp.evaluate(u8"pack .b"); |
まだジオメトリーマネージャーであるpackをラップできていませんが、順番に対応していきたいと思います。