[C++][wxWidgets] あとから親ウィンドウを設定する

 マイナーだけど一部でそれなりに有名なクロスプラットフォームGUIライブラリ「wxWidgets」のマイナーなネタです。

 wxWidgetsでウィンドウ(コンポーネントとかコントロールとか、所謂GUIなアプリを構成する部品を、wxWidgetsではウィンドウと呼びます)を生成するとき、第一引数に親ウィンドウを設定しなければなりません。まぁウィンドウを作る時点で親ウィンドウが決まっている場合がほとんどなのであまり問題にはならないのですが、マニアックな使い方をすると困ることがあります。

 今回はそんなマニアックなシチュエーションと、ウィンドウ生成時の挙動周りのまとめでもしておこうかなと思います。

コンストラクタで親ウィンドウをnullにできるか

 残念ながら、親ウィンドウにnull(0)を指定することはできません。実行時に怒られます。しかし、引数0個のコンストラクタを呼び出すことは可能で、その際は親ウィンドウがnullになるようです。不思議な仕様(ソース見たら「引数0のコンストラクタ呼んでも正しく動かないよ」みたいなこと書いてあった)。

wxTextCtrl(
    wxWindow* parent,
    wxWindowID id,
    const wxString& value = "",
    const wxPoint& pos = wxDefaultPosition,
    const wxSize& size = wxDefaultSize,
    long style = 0,
    const wxValidator& validator = wxDefaultValidator,
    const wxString& name = wxTextCtrlNameStr
)

 例えば、wxTextCtrlのコンストラクタは上記のようになっています。見て分かる通り、parentとidを指定しなければなりません。しかし、以下のコンストラクタを呼ぶことで、すべてのパラメータをデフォルトのままテキストボックスを生成することができます。

wxTextCtrl()

 空っぽです。が、ちゃんと呼べます。

 しかし、この空のコンストラクタで生成したウィンドウは、親ウィンドウのサイザーに登録したとしても、正しく表示されません。アプリケーション終了時にメモリーリークします。つまり、コンストラクタ呼び出し時にちゃんと親ウィンドウを指定しないと登録できません。

 また、ウィンドウサイズの設定や、ウィンドウ座標の設定なども正しく動作しません。Windows環境でしか試していないのですが、親ウィンドウを指定しないとウィンドウハンドルが有効にならず、座標サイズ等に関係するWin32APIの呼び出しに失敗しているようです。

SetParentとReparent

 さて、ここで親ウィンドウを指定するための2種類のメンバ関数を紹介します。

 SetParentは、その名の通り親ウィンドウを設定するためのメンバ関数です。しかし、このメンバ関数を呼んでも、親ウィンドウが変更されるわけではありません。
 wxWidgetsではこの手のトラップがたくさんあるのですが、「Set~~」という名前がついたメンバ関数は基本的にセッターで、プライベートなメンバ変数の値を書き換えるだけです。つまり、書き換えたあとにその値が適用されるように処理されるとは限りません。今回もそのパターンのようで、おそらく「m_Parent」のような内部のメンバ変数が書き換えられるだけで、ウィンドウの親子関係が適切に設定されるわけではないようです。

 そこで使用するのがReparentメンバ関数です。このメンバは、親ウィンドウの書き換えを行うメンバ関数です。引数はSetParentと同じなのですが、こちらは正しく親ウィンドウの設定をしてくれるようです。リファレンスにはWin32とGTK環境下でのみ動作すると書かれていましたが、手元にあるMac OS X(10.7.5)+wxWidgets 2.9.4環境下では問題なく動作しました。

 Reparentは親を入れ替えるだけなので、サイズや座標等設定したあとでも問題なく動作します。

Createメンバ関数を呼ぶという選択肢も…

 最後に、Createメンバ関数を呼ぶ方法です。このCreateメンバ関数は、ウィンドウ生成時にコンストラクタに引数を渡した時に呼ばれるメンバ関数で、親ウィンドウの設定や、その他必要な設定を初期化する際に使用されているようです。このメンバ関数をあとから呼ぶことで、親ウィンドウの設定ができるようになります。

wxTextCtrl *text = new wxTextCtrl();
wxFrame *frame = new wxFrame(
    NULL,
    wxID_ANY,
    wxT("Sample"),
    wxPoint(-1, -1),
    wxSize(600, 400)
);

text->Create(frame, wxID_ANY);

 もっとも、この方法では予めサイズ等を設定できないので、あまり使用するシチュエーションはないとは思いますが…

結論

 現時点では、予め親ウィンドウを指定してウィンドウを生成し、あとからReparentで入れ替える方法が確実なようです。まぁ元々あとから親を設定したい、なんていう状況は滅多にないとは思いますが…念のため。

0 件のコメント :

コメントを投稿