wxPython & XRC で Custom Frame を作る方法3種
@wxPython 4.0.1 (Phoenix)
UI が複雑になってくると、Custom Frame や Custom Panel を作ってコンポーネント化したくなります。
wxPython では wx.Frame
や wx.Panel
を継承した class を作ることでこれを実現できますが、どうもコードベースで実装している例が多いようです。
しかし、View は分離しておきたいもの。特に複雑なUIの場合はなおさらです。 そこで、XRC を使って Custom Frame (Panel) を作る方法を調べてみました。
今回は、例として、Button が一つ中央にある Custom Frame を作ってみます1。
XRCを使った Custom Frame の作り方は私が調べた限りだと以下の3通りあるようです2。 後述しますが、後半2種は使い勝手が悪いので、基本的には Two Stage Creation を使うと良いでしょう。
- Two Stage Creation を使う
- subclass を使う
- XmlResourceHandler を使う
今回使用する XRC を以下に示します3。 3種でほぼ同じですが、subclass を使う場合だけ少し変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <? xml version = "1.0" ?> < resource > < object class = "wxFrame" name = "top" > < object class = "wxBoxSizer" > < orient >wxVERTICAL</ orient > < object class = "sizeritem" > < object class = "wxBoxSizer" > < object class = "sizeritem" > < object class = "wxButton" name = "button" > < label >Click me!</ label > </ object > < flag >wxALIGN_CENTRE_VERTICAL</ flag > </ object > < orient >wxHORIZONTAL</ orient > </ object > < option >1</ option > < flag >wxALIGN_CENTRE_HORIZONTAL</ flag > </ object > </ object > </ object > </ resource > |
Two Stage Creation を使う方法
ここでは wxPython 4.0.1 (Phoenix) での方法を書きます。3以前に関しては以下を参照してください。
Two Stage Creation とは、Window インスタンスの作成時に parent が決まっていない場合、後でUIを生成する方法です。wxWidget ではコンストラクタのオーバーロードによって実現しています。
wxPython 3 系以前では特殊な関数を用意することで同じことを実現していましたが、wxPython 4 (Phoenix) 以降ではより本家に近い実装になったとのこと。
参考
Python コードでは親クラス(wx.Frame クラス)を継承したクラスを作成します。この時、親のコンストラクタを呼ぶ際に、parent
を指定しないのがポイントです。
これにより、後の LoadFrame()
時までUI の生成を遅らせる事が出来ます。
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 | # -*- coding: UTF-8 -*- import wx import wx.xrc as xrc __res = None def get_resources(): global __res if __res is None : __init_resources() return __res def __init_resources(): global __res __res = xrc.XmlResource() __res.Load( "gui.xrc" ) class MyFrame(wx.Frame): def pre_create( self ): """ クラスの初期化時に呼ばれる関数 window の生成前に呼ぶべきカスタムセットアップを記述してください。 SetWindowStyle() や SetExtraStyle() 等。 """ pass def __init__( self , parent): # 引数なしのコンストラクタを呼ぶ wx.Frame.__init__( self ) self .pre_create() # ここで self.Create() が呼ばれる get_resources().LoadFrame( self , parent, "top" ) |
以下のように使用します。
1 2 3 4 5 6 7 8 9 10 11 12 | #!/usr/bin/env python3 # -*- coding: UTF-8 -*- import wx from gui import MyFrame if __name__ = = "__main__" : app = wx.App( False ) frame = MyFrame( None ) frame.Show() app.MainLoop() |
このサンプルコードを最初に見た時、__res
等は MyFrame
の static 属性にした方が良いのではないか?と思いました4。
しかし、これは複数のオブジェクトを1モジュール内に記述することを想定して、このようになっているのです。 具体的な例は以下を参照してください。
subclass を使う方法
この方法では XRC に1点変更を加えます。
以下のように wxFrame の subclass
属性に Custom Frame 名が入ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <? xml version = "1.0" ?> < resource > < object class = "wxFrame" name = "top" subclass = "gui.MyFrame" > < object class = "wxBoxSizer" > < orient >wxVERTICAL</ orient > < object class = "sizeritem" > < object class = "wxBoxSizer" > < object class = "sizeritem" > < object class = "wxButton" name = "button" > < label >Click me!</ label > </ object > < flag >wxALIGN_CENTRE_VERTICAL</ flag > </ object > < orient >wxHORIZONTAL</ orient > </ object > < option >1</ option > < flag >wxALIGN_CENTRE_HORIZONTAL</ flag > </ object > </ object > </ object > </ resource > |
Python コードでは subclass
に設定したクラスを作成します。
XRC によって生成されたリソースへのアクセスは Window 生成完了後でなければなりません。
そのため、EVT_WINDOW_CREATE
を登録します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # -*- coding: UTF-8 -*- import wx import wx.xrc as xrc class MyFrame(wx.Frame): def __init__( self , * args, * * kw): wx.Frame.__init__( self , * args, * * kw) # window create イベント登録 self .Bind(wx.EVT_WINDOW_CREATE, self .on_create) def on_create( self , event): # window create 後でないとリソースへのアクセスはできない self .button = xrc.XRCCTRL( self , "button" ) self .button.Bind(wx.EVT_BUTTON, self .on_click) def on_click( self , event): print ( "clicked" ) |
使用方法は以下です。 クラスの外からリソースファイルを指定しなければなりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/usr/bin/env python3 # -*- coding: UTF-8 -*- import wx import wx.xrc as xrc if __name__ = = "__main__" : app = wx.App( False ) # 以下の処理を wx.App を継承したクラスに書いてあるものが多いが、見やすさ重視で res = xrc.XmlResource( "gui.xrc" ) frame = res.LoadFrame( None , "top" ) frame.Show() app.MainLoop() |
この方法、Python と XML に相互依存があるという問題があります。
また、EVT_WINDOW_CREATE
後でないとリソースアクセスできないのも少々使い勝手が悪いです。
XmlResourceHandler を使う方法
この方法では XRC を変更する必要はありません(subclass
を指定する必要はありません)。
MyFrame
クラスは subclass
を使用するものと全く同じです。
そのかわり、xrc.XmlResourceHandler
を継承したクラスを作成し、 InsertHandler
で登録します。
この、xrc.XmlResourceHandler
は XML から node をインスタンス化する方法を提供するものです。
CanHandle
で特定の node を指定し、DoCreateResource
でインスタンス化します。
なお、複数の xrc.XmlResourceHandler
を登録することが出来ますが、場合によっては順番が重要なので注意が必要です5。
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 | # -*- coding: UTF-8 -*- import wx import wx.xrc as xrc class MyFrameXmlHandler(xrc.XmlResourceHandler): def CanHandle( self , node): # DoCreateResource を呼ぶ条件を指定 # 今回の場合、wxFrame だけで大丈夫だが、現実的には名前のチェックも入れたほうが良い return self .IsOfClass(node, "wxFrame" ) and node.GetAttribute( "name" ) = = "top" def DoCreateResource( self ): frame = MyFrame( None ) # XML から子クラスを生成してぶらさげる self .CreateChildren(frame) return frame class MyFrame(wx.Frame): def __init__( self , * args, * * kw): wx.Frame.__init__( self , * args, * * kw) # window create されてからリソースアクセスするためにイベント登録 self .Bind(wx.EVT_WINDOW_CREATE, self ._on_create) def _on_create( self , event): self .button = xrc.XRCCTRL( self , "button" ) self .button.Bind(wx.EVT_BUTTON, self ._on_click) def _on_click( self , event): print ( "clicked" ) |
使用方法は以下です。 この方法もクラスの外からリソースファイルを指定しなければなりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/usr/bin/env python3 # -*- coding: UTF-8 -*- import wx import wx.xrc as xrc if __name__ = = "__main__" : app = wx.App( False ) res = xrc.XmlResource( "gui.xrc" ) # ここで InsertHandler を呼ぶのがポイント res.InsertHandler(MyFrameXmlHandler()) frame = res.LoadFrame( None , "top" ) frame.Show() app.MainLoop() |
相互依存は無くなりましたが、XMLリソースの指定や xrc.XmlResourceHandler
の設定を MyFrame
の外で行わないといけないのは、やはりイマイチです。
また、EVT_WINDOW_CREATE
後でないとリソースアクセス出来ない使い勝手の悪さもあります。
0 件のコメント:
コメントを投稿