wxPython & XRC で Custom Frame を作る方法2種+α
[wxPython & XRC で Custom Frame を作る方法3種 | 穀風](https://kokufu.blogspot.com/2018/03/wxpython-xrc-custom-frame-3.html)
@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 の作り方は以下の2通りあるようです。 ただ、基本の使い方だと少々使いづらいので、私は少し使い方を変更して使っています。最後にそちらも紹介します。
- subclass を使う
- XmlResourceHandler を使う
subclass を使う方法
まずは XRC でレイアウトを作成します2。
この時、トップレベルの 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 = "main.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 で指定したクラスの子クラスにします。
上記の例だと wx.Frame
です。
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 21 22 23 24 25 26 27 28 29 30 31 32 | import wx import wx.xrc as xrc class MyFrame(wx.Frame): def __init__( self , * args, * * kw): super ().__init__( * 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" ) def main(): app = wx.App( False ) # 以下の処理を wx.App を継承したクラスに書いてあるものが多いが、見やすさ重視で res = xrc.XmlResource( 'MyFrame.xrc' ) frame = res.LoadFrame( None , "top" ) frame.Show() app.MainLoop() if __name__ = = '__main__' : main() |
この方法、Python と XML に相互依存があって、私はあまり好きになれません。
XmlResourceHandler を使う方法
XmlResourceHandler
を使用する方法の場合、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 > |
MyFrame
クラスは先のものと全く変わりません。
そのかわり、xrc.XmlResourceHandler
を継承したクラスを作成し、 InsertHandler
で登録します。
この、xrc.XmlResourceHandler
は XML から node をインスタンス化する方法を提供するものです。
CanHandle
で特定の node を指定し、DoCreateResource
でインスタンス化します。
なお、複数の xrc.XmlResourceHandler
を登録することが出来ますが、場合によっては順番が重要なので注意が必要です3。
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 | 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): super ().__init__( * 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" ) def main(): app = wx.App( False ) res = xrc.XmlResource( 'MyFrame.xrc' ) # ここで InsertHandler を呼ぶのがポイント res.InsertHandler(MyFrameXmlHandler()) frame = res.LoadFrame( None , "top" ) frame.Show() app.MainLoop() if __name__ = = '__main__' : main() |
相互依存は無くなりましたが、XMLリソースの指定や xrc.XmlResourceHandler
の設定が MyFrame
の外で行われているのはやはりイマイチです。
我流に修正
個人的には、以下のようにインスタンスを生成して Show
するだけで使用できるべきだと思います。
1 2 3 4 | app = wx.App( False ) frame = MyFrame( None ) frame.Show() app.MainLoop() |
というわけで、理想の形になるよう修正してみました。
ポイントは MyFrameXmlHandler
を __init__
の中で実装していることです。
これにより、自身を xrc.XmlResourceHandler
中で扱えるので、XLM の指定からインスタンス生成までを MyFrame
クラス内に閉じる事が出来ます。
また、副次的効果として、リソースへのアクセスが __init__
内で行えるようになりました。
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 | import wx import wx.xrc as xrc class MyFrame(wx.Frame): def __init__( self , * args, * * kw): super ().__init__( * args, * * kw) this_frame = self class MyFrameXmlHandler(xrc.XmlResourceHandler): def CanHandle( self , node): return self .IsOfClass(node, "wxFrame" ) and node.GetAttribute( 'name' ) = = 'top' def DoCreateResource( self ): self .CreateChildren(this_frame) return this_frame res = xrc.XmlResource( 'MyFrame.xrc' ) res.InsertHandler(MyFrameXmlHandler()) # 以下を呼ぶことで XML からインスタンス化される res.LoadFrame( None , "top" ) # __init__ 内でリソースアクセス出来る self .button = xrc.XRCCTRL( self , 'button' ) self .button.Bind(wx.EVT_BUTTON, self ._on_click) def _on_click( self , event): print ( "clicked" ) def main(): app = wx.App( False ) frame = MyFrame( None ) frame.Show() app.MainLoop() if __name__ = = '__main__' : main() |
0 件のコメント:
コメントを投稿