wxPythonのwxImageで画像を表示する

画像を読み込んで表示させる簡単な例です。(file open error == IOErrorの処理を入れないといけない)
利用するクラスはwx.Image。初期化で読み込む画像ファイルパスを指定します。
Imageインスタンスそのままでは表示できなくて、bitmapに変換してから初めて表示できます。
StaticBitmapで要素に画像を張り付けることができるみたいです。

import wx
class myFrame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title)
        image = wx.Image('test.jpg')
        self.bitmap = image.ConvertToBitmap()

        wx.StaticBitmap(self, -1, self.bitmap, (0,0), self.GetClientSize())
        self.SetSize(image.GetSize())

app = wx.App(False)
frame = myFrame(None, "Image Viewer")
frame.Show()
app.MainLoop()

バイスコンテキストを取得してbitmapを描画する方法でも画像を表示(描画)できます。
バイスコンテキストはwx.PaintDC(window)で得られるのですが、
"Paintイベント"のときにしか取得できないので、onPaintイベントハンドラを作成します。

import wx
class myFrame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        image = wx.Image('test.jpg')
        self.bitmap = image.ConvertToBitmap()

        self.SetSize(image.GetSize())

    def OnPaint(self, event=None):
        deviceContext = wx.PaintDC(self)
        deviceContext.Clear()
        deviceContext.SetPen(wx.Pen(wx.BLACK, 4))
        deviceContext.DrawBitmap(self.bitmap, 0, 0, True)

app = wx.App(False)
frame = myFrame(None, "Image Viewer")
frame.Show()
app.MainLoop()

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()

wxPythonでGUIアプリを作る GUIコンポーネントとサイザー

GUIを作るために必要な要素(Frame,button,text,textcontrolなど)はwxWindowクラスの派生クラスとして提供されています。GUIを設計するためにこれらの要素を一つずつフォームに配置していきます。
例えばwxFrameがwxMuneBarやwxPaneを含んでいて、さらにwxPaneがwxStaticTextなどを含んでいる場合にはこんな階層構造をとることになります。

wxFrame
-> wxMuneBar
-> wxPanel
-> wxStaticText wxTextCtrl
-> wxStaticText wxTextCtrl
wxPanel
-> wxStaticText wxTextCtrl
-> wxStaticText wxTextCtrl

要素をそのまま配置すると位置やサイズが常に固定になってしまいますが、Sizerクラスを利用すると自動的にレイアウトを整えることができます。Frameの大きさが変化しても、Sizerが各要素のサイズと位置を自動的に計算して再配置してくれます。
実際に使うにはwx.Sizerから派生したサブクラスwx.BoxSizer,wx.GridSizerなどを利用します。wx.BoxSizerは水平・垂直方向に要素を整形し、wx.GridSizerは要素を自動で格子状に配置してくれます。Sizerの中にSizerを追加して入れ子にすることもできます。

次のサンプルはwx.BoxSizerを使ったコントロール配置例です。wx.BoxSizerを宣言するときに引数としてレイアウトの方向を指定します。

import wx

class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(400,400))
        #子サイザーを用意する(水平方向に整形)
        self.childSizer = wx.BoxSizer(wx.HORIZONTAL)
        #ボタンを作成する
        self.startButton =wx.Button(self, -1, "Start")
        self.stopButton =wx.Button(self, -1, "Stop")        
        #子サイザーにボタンを追加する
        self.childSizer.Add(self.startButton, 0, wx.EXPAND)
        self.childSizer.Add(self.stopButton, 0, wx.EXPAND)
        #メインサイザーを用意する(垂直方向に整形)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        #テキストボックスとラベルを作成する
        self.textControl = wx.TextCtrl(self, size=(300,100), style=wx.TE_MULTILINE)     
        self.label = wx.StaticText(self, label="Label :")
        #サイザーに追加する
        self.sizer.Add(self.textControl, 0, wx.EXPAND)
        self.sizer.Add(self.childSizer, 0, wx.ALIGN_CENTER)
        self.sizer.Add(self.label, 0, wx.EXPAND) 
        #Frameにサイザーを追加する
        self.SetSizer(self.sizer)
        #サイザーのレイアウトを行う
        self.SetAutoLayout(1)
        self.sizer.Fit(self)

        self.Show()
        
app = wx.App(False)
frame = MainWindow(None, "sample")
app.MainLoop()

要素を登録するにはAddメソッドを使います。引数として、Sizerに追加する要素、proportion 、レイアウトを行うためのflagを指定します。
proportion が0の場合は要素のサイズを変えず、1の場合はフレームのサイズと連動して要素のサイズも変更してくれるみたいです。
flagの値を変えると、レイアウトをどのような規則で行ってくれるのかを指定することができます。(例えばwx.ALIGN_CENTERなら中央に配置するなど)
作成したサイザーを、SetSizerメソッドでFrameに追加します。その後SetAutoLayoutやFitによって自動レイアウトを行っています。