こんにちは、高木です。
今回はTkのジオメトリーマネージャーについて考えることにします。Tkのジオメトリーマネージャーには次の3種類があります。
packerはウィジェットを上下左右にどんどん積めていきます。placerはX, Y座標を指定してウィジェットを配置します。gridderは行と列を指定して格子状にウィジェットを配置します。Tkではこれらのジオメトリーマネージャーを使ってウィジェットをトップレベルのウィンドウに配置していきます。
それぞれのジオメトリーマネージャーを比べてみると、共通部分が多いことに気付きます。できれば、そうした共通部分は基底クラスにまとめてあげたいものです。ところが、一番基本的なコマンド(configureやforgetなどがなく、pack, place, gridのあとにパラメーターを並べるタイプのコマンド)がplaceだけちょっと違っています。
packとgridは次の形式です。
0 1 2 |
コマンド名 window ?window ...? ?options? |
これに対して、placeは次のような形式になっています。
0 1 2 |
place window option value ?option value ...? |
こういうときは落ち着いてよく見ると、共通部分が見えてくるはずです。
packやgridのoptionsも、option valueの並びだということがわかるはずです。たとえば、-anchor nwのようにです。また、placeはwindowを1個しか指定できませんが、1個以上のwindowを指定できると考えれば同じ形式になります。
よって、次のようにまとめることができるはずです。
0 1 2 |
コマンド名 window ?window ...? option value ?option value ...? |
このうちoptionとvalueは、widgetクラスのコンストラクターなどで指定するオプションと同じ形式ですのでobj型で渡せばOKです。windowはwidgetクラスを渡したいところですが、それだと派生クラスを渡した際にスライスが起きてしまいます。しかたがないので、windowクラスを別途用意して、const widget&からのコンストラクターを持たせてあげることにします。
それではgeometry_managerクラスのコードを見ていきましょう。
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
namespace tk { class geometry_manager { public: class window { public: window(const widget& w) : ref_(w) { } const widget& get() const { return this->ref_; } private: const widget& ref_; }; // 基本コマンド int operator()(std::initializer_list<window> windows, std::initializer_list<tcl::obj> options) const { std::vector<Tcl_Obj*> args{ this->type_.get() }; Tcl_Interp* interp = nullptr; std::for_each(windows.begin(), windows.end(), [&args, &interp](auto w) { // 各ウィンドウが所属しているインタープリターの一致判定 if (interp == nullptr) interp = w.get().interpreter().get(); else if (interp != w.get().interpreter().get()) throw std::runtime_error("異なるインタープリターに所属するウィジェットが混在している"); args.push_back(w.get().path().get()); }); std::for_each(options.begin(), options.end(), [&args](auto option) { args.push_back(option.get()); }); return tcl::interpreter(interp).evaluate(args); } tcl::obj type() const { return this->type_; } virtual ~geometry_manager() noexcept = default; protected: explicit geometry_manager(const tcl::obj& type) : type_(type) { } private: tcl::obj type_; }; } |
こんな感じで最低限のコマンドを実現することができます。これだけだとoptionsを必ず指定しないといけないので、optionsを省略したバージョンも多重定義しておくといいかもしれませんね。windowsに関しても1個だけのバージョンを用意しておくとよさそうです。無駄に長くなるので、いずれも今回は割愛します。
次に、このgeometry_managerクラスを派生してpackerクラスを作ってみます。placerやgridderも同じですので今回は省略します。
0 1 2 3 4 5 6 7 8 9 10 11 |
namespace tk { class packer : public geometry_manager { public: packer() : geometry_manager(u8"pack") { } }; } |
ここまでできれば、何とかウィジェットを配置できるようになります。
0 1 2 3 4 5 6 |
tk::widget b(u8"button", tk::widget(interp), u8"b", { u8"-text test", u8"-command test" }); tk::label l(tk::widget(interp), u8"l", { u8"-text hello" }); tk::packer pack; pack({ l, b }, {}); |
ほぼTcl/Tkのイメージ通りのコードでジオメトリーマネージャーが実現できました。
次回はジオメトリーマネージャーについてもう少し深掘りしてみましょう。