悪習慣にご注意!

トップレベルでは「wx.Frame」を使おう!

tags:python, gui, tips
created:2007-02-05T20:24:24

なんで、トップレベルに「wx.Dialog」?! 悪い習慣を捨てよう!

はじめに

「wxPython」利用でよくあるトラブルのひとつを紹介します。

ここではたまたま「wxWidgets」と共通のテーマになりましたので、 「wxWidgets」ユーザは

  • 「wxPython」を「wxWidgets」に
  • 「wx.???」を「wx???」に

読み替えてもらうといいでしょう。

問題点

なぜか、シンプルなシングルウインドウのアプリを構築する際、 「wx.Dialog」をトップレベルウインドウとして用いようとする人が多数。 「wx.Dialog」はあくまで問い合わせのためのウインドウ用です。

最小のサンプルとか見て頂ければわかってもらえると思いますが、 「wx.Frame」を用いてトップレベルウインドウを作成しています。 決して「wx.Frame」はMDIのためのクラスなどではありません。

なぜ、サンプルを無視してまで「wx.Dialog」を使おうとするのかが謎でしたが、 どうやら彼らには「MFCを利用した経験」があるというあたりに原因がありそうです。

注釈

MFCでの想定は

「CFrmaeWndを用いてシンプルなトップレベルウインドウを構築する」

というあたりだった様ですが、開発者が主に利用したいウインドウスタイルが 「CFrmaeWnd」より「CDialog」のほうが近かったということでしょう。

「CFrameWnd」では、メニューやツールバーがどうしてもくっついてきて、 それらの削除が入門記事ではふさわしくないので「CDialog」を用いた 入門ドキュメントが氾濫し、 「CDialog」でトップレベルウインドウを賄う風習が広まったと考えられます。

「wxPython」のインスタンス管理

「wxPython」ではあるコントロールについて、

  • ラッパークラスインスタンス
  • OS上のリソース

という2つのメモリオブジェクトを平行管理しています。

例えば、「wx.Frame」をインスタンス化すると同時に OS上にウインドウオブジェクトの生成を行います。 そのインスタンスを破棄するとき、 OS上のウインドウオブジェクトも一緒に破棄します。

そして最も重要な点として「wxPython」では、 基本的にインスタンス管理の責任を親に委託する仕組みを取っています。

ほとんどのコントロールはインスタンス化するとき、 親インスタンスを指定する様になっています。 親インスタンスを指定してインスタンス化したコントロールは インスタンス管理責任を親に委託してしまいます。

親ありコントロールは親が破棄されるときに親によって破棄されます。 当然孫、ひ孫とリカーシブに破棄されます。

正しいヒエラルキーを維持して構築されているウインドウは、 トップレベルウインドウを破棄するだけで すべてのコントロールがちゃんと破棄されます。

安定感のあるアプリケーションを構築するには すべてのインスタンスの根っこにあるのは たった一つのトップレベルウインドウにしておき、 アプリケーションの終了はそのトップレベルウインドウの破棄と対応という形がオススメです。

ガベージコレクションや、OSのプロセス管理によるリソース破棄に頼ると おかしなエラーに悩まされます。 これらの管理方法では破棄する順番を考慮してくれないのです。 どちらか一方の管理だけなら問題ないのですが、 wxPythonではメモリインスタンスとOSリソースを平行して利用しているので ちぐはぐになってしまうようです。

「親なしコントロール」のインスタンス管理責任は開発者にあります。 破棄される順番には十分に注意しましょう。 (下手するとPythonのガベージコレクタのことを勉強しないと解決しない問題に出くわします。)

「親なしコントロール」になれるのは特殊

一応、以下の2つのベースクラスが親なしでのインスタンス化が可能になっています。

  • 「wx.Frame」
  • 「wx.Dialog」

「wx.Dialog」はモーダル問い合わせウインドウになるべくして、 「OK」や「キャンセル」などの問い合わせ用イベントのデフォルト動作が実装されています。

一方「wx.Frame」はトップレベルウインドウになるべくして クローズイベント時に自分自身の破棄を行うようデフォルト動作が実装されています。

つまり、「wx.Dialog」をトップでOKイベントやキャンセルイベントを投げてしまうと、 勝手に閉じてしまったりするわけです。

また、ダイアログという性質上、クローズしたあと各コントロールの値を回収するためクローズしたからといって自分を破棄したりしません。 (前述の理由から、「wx.Frame」を除くすべてのウインドウやコントロールは 自分で自分を破棄する機構を持っていません。)

当然閉じたことを検出したらトップレベルウインドウの 「wx.Dialog」インスタンスを開発者が意識して破棄しないと、 メインループは終了しませんよ~。

まとめ

ダイアログをトップレベルに使うなんてことがそもそもイレギュラーだということです。

つまり、「wx.Dialog」を想定されていない使い方で トップレベルウインドウにしてもろくなことにはなりませんよ!という話。

要するに適材適所。

  • トップレベルウインドウには迷わず「wx.Frame」を使いましょう。
  • 「wx.Dialog」はモーダルとして一時的に使いましょう。
  • すべてのインスタンスの親をたどればたった一つの「wx.Frame」インスタンス

この3つを守れば、ソースコードはシンプルに。 そして起動や終了もスムーズなアプリケーションが構築できますよ!