wxPythonでGUIアプリを作る 〜XRCでGUI設計

XRC

wxPythonでGUIアプリを作る GUIコンポーネントとサイザー - white wheelsのメモの続き。
GUIを作るには各コンポーネントをサイザーの上に載せて設計するというのが前回の話です。
簡単なGUIならまだしも、複雑なインターフェースを作ろうとすると、コードを手打ちで作っていくスタイルではあまりにも作業量が多くなってしまいます。
GUI要素の配置に関する情報をコードのロジックから分離して、データ構造として取り出しておくことができれば非常に使いやすいと思います。実はwxPythonではGUIアプリのレイアウトの情報をXMLファイルに分離してしまうXRCという手法があります。
例えば、次のようなGUIを考えてみます。


wx.Frame

wx.BoxSizer
wx.TextCtr
wx.Button
XRCを利用しない場合のコードは下のようになります。

import wx
class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title)
        #textboxとbuttonを作成する
        self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
        self.button = wx.Button(self, -1, "OK")
        #sizerに登録する
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.control, 0, wx.EXPAND)
        self.sizer.Add(self.button, 0, wx.EXPAND)
        #sizerをframeにを追加する
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)
        self.Show()

app = wx.App(False)
frame = MainWindow(None, "Sample")
app.MainLoop()

XRC(xmlファイル)にレイアウトの情報を分離したコードが次の例です。gui.xrcはTestXRC.pyと同じディレクトリに置きます。

gui.xrc
<?xml version="1.0" ?>
<resource>
  <object class="wxFrame" name="myFrame">
    <object class="wxBoxSizer">
      <orient>wxVERTICAL</orient>
      <object class="sizeritem">
        <object class="wxTextCtrl"/>
      </object>
      <object class="sizeritem">
        <object class="wxButton" name="myButton">
          <label>OK</label>
        </object>
      </object>
    </object>
  </object>
</resource>
TestXRC.py
import wx
from wx import xrc
class MyApp(wx.App):

    def OnInit(self):
        self.res = xrc.XmlResource('gui.xrc')
        self.init_frame()
        return True

    def init_frame(self):
        self.frame = self.res.LoadFrame(None, 'myFrame')
        self.frame.Show()

if __name__ == '__main__':
    app = MyApp(False)
    app.MainLoop()

from wx import xrcを忘れないようにしましょう。
最初の例と違ってwx.Appをクラス化していて、初期化用のメソッドの中でFrameを作成しています。
そこでgui.xrcを読み込んでいることが分かります。要素のレイアウトはxmlの構造から自動的に作成してくれます。
結局GUI設計をするには次の2点の作業を行うとよいことが分かります。

  • レイアウトの情報を記録したxmlを作成する
  • xmlの読み込みとイベントをBindするコードを作成する

イベント処理の追加

先ほど作成したGUIにイベント処理を追加してみます。ボタンを押すとOnButtonPressedが呼び出されるようにしてみます。
するべきことは、ボタンの要素を取得することと、ボタンとイベントを結びつけることです。
xmlノードから特定の要素を取得するにはxrc.XRCCTRLを使います。例えばname="myButton"の要素を取り出すには、

button =  xrc.XRCCTRL(self.frame, 'myButton')

という処理を行います。
イベントと要素を結びつける処理は前回のようにFrameクラスのBindメソッドを使います。

self.frame.Bind(wx.EVT_BUTTON, self.OnButtonPressed, button)

結局コードとしては下のようになりました。

TestXRC.py
class MyApp(wx.App):

    def OnInit(self):
        self.res = xrc.XmlResource('gui.xrc')
        self.init_frame()
        return True

    def init_frame(self):
        self.frame = self.res.LoadFrame(None, 'myFrame')
        button =  xrc.XRCCTRL(self.frame, 'myButton')

        self.frame.Bind(wx.EVT_BUTTON, self.OnButtonPressed, button)
        self.frame.Show()

    def OnButtonPressed(self, evt):
        print "submit"


if __name__ == '__main__':
    app = MyApp(False)
    app.MainLoop()

XRCedを使ったGUI設計

GUIのレイアウトに対応したxrcファイルを手書きで作るのはかなり大変な作業です。
それを読み込むコードも作成するのが面倒な場合が多いと思います。
そこでビジュアルにxrcを作成できるツールがいくつかあります。

  • XRCed
  • wxGlade
  • wxFormBuilder
  • ....他いろいろ

XRCedはxmlのツリーを作成する機能があります。wxPythonに標準で付属されているため、wxPythonをインストールすれば使うことができます。
そこでこのツールを使ってGUI設計をしてみることにします。
wxPython>XRC Resource Editorを起動します。

GUI要素の配置

XRCedは要素選択と、レイアウトのツリー構造&要素のプロパティ設定の2つのウインドウから成っています。

Frameの作成

WindowsウインドウでwxFrameを選択します。name欄をmyFrameにしておきます。

Sizerの作成

SizersウインドウでwxBoxSizerを選択します。

TextControlの配置

wxBoxSizerを選択した状態で、ControlsウインドウのwxTextCtrlを選択します。
SizerにwxTextCtrlが追加されました。

Buttonの配置

同じくwxBoxSizerを選択した状態で、ControlsウインドウのwxButtonを選択します。
nameをmyButtonにしておきます。また、labelをOKにします。

XRCファイルの作成

File>Save Asで"gui.xrc"として保存します。これでxrcファイルを作成することができました。
上で紹介したgui.xrcと基本的に同じものが作成されています。

GUIアプリの作成

あとは、アプリ側のコードを実装すれば終わりですが、上で紹介したTestXRC.pyをそのまま使っても大丈夫です。
XRCedに付属しているpythonモジュール生成機能を使うのが良いと思います。
モジュール生成はFile>Generate Pythonを選択してください。次のようなコードが自動生成されます。

gui_xrc.py
# This file was automatically generated by pywxrc.
# -*- coding: UTF-8 -*-
import wx
import wx.xrc as xrc

__res = None

def get_resources():
    """ This function provides access to the XML resources in this module."""
    global __res
    if __res == None:
        __init_resources()
    return __res

class xrcmyFrame(wx.Frame):
#!XRCED:begin-block:xrcmyFrame.PreCreate
    def PreCreate(self, pre):
        """ This function is called during the class's initialization.
        
        Override it for custom setup before the window is created usually to
        set additional window styles using SetWindowStyle() and SetExtraStyle().
        """
        pass
        
#!XRCED:end-block:xrcmyFrame.PreCreate

    def __init__(self, parent):
        # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
        pre = wx.PreFrame()
        self.PreCreate(pre)
        get_resources().LoadOnFrame(pre, parent, "myFrame")
        self.PostCreate(pre)

        # Define variables for the controls, bind event handlers

# ------------------------ Resource data ----------------------

def __init_resources():
    global __res
    __res = xrc.EmptyXmlResource()

    __res.Load('gui.xrc')

今度はFrameをクラス化していることに注意してください。
ここにイベントハンドラ(ボタンが押された時の処理)、イベントとのBind処理、
アプリの実行コード(別のモジュールにする方がいいかも)を書けば終了です。

import wx
import wx.xrc as xrc

__res = None

def get_resources():
    """ This function provides access to the XML resources in this module."""
    global __res
    if __res == None:
        __init_resources()
    return __res

class xrcmyFrame(wx.Frame):
#!XRCED:begin-block:xrcmyFrame.PreCreate
    def PreCreate(self, pre):
        """ This function is called during the class's initialization.
        
        Override it for custom setup before the window is created usually to
        set additional window styles using SetWindowStyle() and SetExtraStyle().
        """
        pass
        
#!XRCED:end-block:xrcmyFrame.PreCreate

    def __init__(self, parent):
        # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
        pre = wx.PreFrame()
        self.PreCreate(pre)
        get_resources().LoadOnFrame(pre, parent, "myFrame")
        self.PostCreate(pre)

        # Define variables for the controls, bind event handlers
        #ボタンをXRCから取得する
        button =  xrc.XRCCTRL(self, 'myButton')#追加
        #ボタンイベントとハンドラを結びつける
        self.Bind(wx.EVT_BUTTON, self.OnButtonPressed, button)#追加
        self.Show()#追加
        
    #ボタンイベントハンドラ
    def OnButtonPressed(self, evt):#追加
        print "submit"#追加

# ------------------------ Resource data ----------------------

def __init_resources():
    global __res
    __res = xrc.EmptyXmlResource()

    __res.Load('gui.xrc')

#ここから追加:アプリの起動処理
app = wx.App(False)
frame = xrcmyFrame(None)
app.MainLoop()