WxPythonでGUIアプリを書く

http://wiki.wxpython.org/index.cgi/Getting_Started を見ながら練習。
インストールは、Python2.4を入れたあとでWxPythonのページからバイナリを落として終了。

何もしないウィンドウを作る。

まずは何もないウィンドウを出してみる。
import wx

app = wx.wxPySimpleApp()
frame = wx.Frame(None,-1,'Hello, world!')
frame.Show(1)
app.MainLoop()
ウィンドウが一つ出てくれば終了です。
次はテキストコントロールを置いてみる。
import wx

class MainWindow(wx.Frame):
    """ We simply derive a new class of Frame. """
    def __init__(self,parent,id, title):
        wx.Frame.__init__(self,parent,wx.ID_ANY,title,size=(800,600))
        self.control = wx.TextCtrl(self,1,style=wx.TE_MULTILINE)
        self.Show(True)

app = wx.PySimpleApp()
frame=MainWindow(None,-1,'Small editor')
app.MainLoop()
今回はMainWindow.__init__()の中でself.Show()を呼んでいるから、外部からShow()を呼ぶ必要はない。でも、色んなイベントを受け付けるためにMainLoop()は呼ばないといけない(そうでないと、ウィンドウが現れた瞬間プログラムが終了してしまう)

MenuBarをつけてみる。
import wx

ID_ABOUT=101
ID_EXIT=110

class MainWindow(wx.Frame):
    def __init__(self,parent,id,title):
        wx.Frame.__init__(self,parent,wx.ID_ANY, title, size = (200,100))
        self.control = wx.TextCtrl(self, 1, style=wx.TE_MULTILINE)
        
        # ステータスバーを作る
        self.CreateStatusBar()

        # まずはメニュー項目を作る (プルダウンで出てくるもの)
        filemenu= wx.Menu()
        filemenu.Append(ID_ABOUT, "&About"," Information about this program")
        filemenu.Append(ID_EXIT,"E&xit"," Terminate the program")

        # 次に、メニュー項目が並んだメニューバーを作って、filemenuをセットする
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu,"&File")

        # ウィンドウにmenubarをセットする
        self.SetMenuBar(menuBar)
        

        self.Show(True)

app = wx.PySimpleApp()
frame = MainWindow(None, -1, "Sample editor")
app.MainLoop()
ID_ABOUTとID_EXITは、メニューに対応したIDで、自分で振らないといけない。 メニューの項目を識別する(どれがクリックされたか、とか)は、このIDで決まる。

イベントを扱う

いまのところ、タイトルバーとステータスバーのあるウィンドウが出るけど、まだクリックしても何も起こらない。
例えばAboutをクリックしたり、Exitをクリックしたときに何か起こるようにしてみる。 イベントを扱う関数を書いて、これと「ボタンのクリック」といった動作を
wx.EVT_MENU(self, ID_ABOUT, self.OnAbout)
のような一行でつなぎ合わせる。 具体的には、下のような感じになる。
import wx

ID_ABOUT=101
ID_EXIT=110

class MainWindow(wx.Frame):
    def __init__(self,parent,id,title):
        wx.Frame.__init__(self,parent,wx.ID_ANY, title, size = (200,100))
        self.control = wx.TextCtrl(self, 1, style=wx.TE_MULTILINE)

        # ステータスバーを作る
        self.CreateStatusBar()

        # まずはメニュー項目を作る (プルダウンで出てくるもの)
        filemenu= wx.Menu()
        filemenu.Append(ID_ABOUT, "&About"," Information about this program")
        filemenu.Append(ID_EXIT,"E&xit"," Terminate the program")

        # 次に、メニュー項目が並んだメニューバーを作って、filemenuをセットする
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu,"&File")

               
        # メニューと動作を関連づける
        wx.EVT_MENU(self, ID_ABOUT, self.OnAbout) 
        wx.EVT_MENU(self, ID_EXIT, self.OnExit)
        
        # ウィンドウにmenubarをセットする
        self.SetMenuBar(menuBar)
        self.Show(True)


           
    # 具体的に呼ばれる関数を書く
    def OnAbout(self, e):
        #新しいメッセージボックスを作ってみる
        d = wx.MessageDialog( self, " A sample editor \n"
                             " in wxPython","About Sample Editor", wx.OK)
        d.ShowModal()
        d.Destroy()


    def OnExit(self,e):
        self.Close(True)
    

app = wx.PySimpleApp()
frame = MainWindow(None, -1, "Sample editor")
app.MainLoop()

PILを用いて画像のロード

このままチュートリアルを続けてもいいけど、僕がGUIを使おうと思った理由は画像処理なので、 そっちに移ってみます。参考になりそうなページは こことか。
画像ファイルを扱うには、PIL (Python Imaging Library)を用いるのが楽。 まずはここからダウンロードしてインストール。

適当な画像ファイルを表示するウィンドウを作ってみる。 1.pngという画像ファイルを、同じディレクトリに置いておき、実行します。
import wx, os, sys
import Image,ImageDraw


class MainWindow(wx.Frame):
    def __init__(self,parent,id,title):
        wx.Frame.__init__(self, parent, wx.ID_ANY, title, size = (400,400))
        # 描画関数を登録
        wx.EVT_PAINT(self, self.OnPaint)

        #まずはPILのイメージとして読み込む
        pilimage = Image.open("1.png")

        #wxの空のイメージを作って、そこにpilimageをコピー
        wximage = wx.EmptyImage(pilimage.size[0], pilimage.size[1])
        wximage.SetData(pilimage.convert('RGB').tostring())
        self.bmp = wximage.ConvertToBitmap()
        
        self.Show(True)

    def OnPaint(self, evt):
        dc = wx.PaintDC(self)
        dc.DrawBitmap(self.bmp, 0, 0, True)


app = wx.PySimpleApp()
frame = MainWindow(None, -1, "Imaging")
app.MainLoop()
wxWidgetの画像関連の関数は割と貧弱なので、PILを使ってファイルをロードし、それを変換しています。
        wximage.SetData(pilimage.convert('RGB').tostring())
の一行で、pilimageを文字列表現にして、それを元にwximageを作っています。
機能的にはこれで十分なのですが、Pythonっぽくオブジェクト指向にして、ビットマップのファイルをオブジェクトにしてみます。
import wx, os, sys
import Image,ImageDraw


class MyImage:
    def __init__(self, fn = None):
        if fn is not None: self.load(fn)

    def load(self, fn):
        self.pilimage = Image.open("1.png")
        self.wximage = wx.EmptyImage(self.pilimage.size[0], self.pilimage.size[1])
        self.wximage.SetData(self.pilimage.convert('RGB').tostring())
        self.bmp = self.wximage.ConvertToBitmap()

    def draw(self, dc):
        dc.DrawBitmap(self.bmp, 0, 0, True)


class MainWindow(wx.Frame):
    def __init__(self,parent,id,title, img):
        wx.Frame.__init__(self, parent, wx.ID_ANY, title, size = (400,400))
        wx.EVT_PAINT(self, self.OnPaint)
        self.img = img
        self.Show(True)

    def OnPaint(self, evt):
        dc = wx.PaintDC(self)
        self.img.draw(dc)


app = wx.PySimpleApp()
frame = MainWindow(None, -1, "Imaging", MyImage("1.png"))
app.MainLoop()
機能は同じです。

PILを用いて画像のピクセル操作

ピクセル操作をするには、PILのImageDrawを用います。
        pildraw = ImageDraw.Draw(self.pilimage)
        pildraw.point((0,self.cnt),fill=(127,127,255))
        # ...

        self.wximage.SetData(self.pilimage.convert('RGB').tostring())
        self.bmp = self.wximage.ConvertToBitmap()
        self.cnt += 1