wxPython入門(大阪Pythonユーザの集まり2014/03)

29
wxPython入門 増田 (@whosaysni)

description

大阪Pythonユーザの集まり 2014/03 で発表した、wxPython入門のスライドです。

Transcript of wxPython入門(大阪Pythonユーザの集まり2014/03)

Page 1: wxPython入門(大阪Pythonユーザの集まり2014/03)

wxPython入門 増田 (@whosaysni)

Page 2: wxPython入門(大阪Pythonユーザの集まり2014/03)

自己紹介 •  増田 泰 (@whosaysni) •  Google+ つかってません •  GitHub<<<BitBucket

•  某バイオベンチャー勤務 •  ドキュメント翻訳とかDjango本とか •  一般社団法人PyConJP理事 •  PyConJPきてください 9/13-15 •  http://2014.pycon.jp/

Page 3: wxPython入門(大阪Pythonユーザの集まり2014/03)

wxPython •  GUIライブラリ •  wxWidgetsのPythonラッパー •  SWIG使ってる •  各OSのGUIコンポーネントを使う •  OSXならCocoa •  Windowsならwin32 •  LinuxならGTK

•  wxWidgetsライセンス •  条文はLGPL2だが、バイナリの配布時に独自の使用許諾ができる例外条項がある

Page 4: wxPython入門(大阪Pythonユーザの集まり2014/03)

まずは基本

Page 5: wxPython入門(大阪Pythonユーザの集まり2014/03)

GUIプログラム •  シェルで起動 •  操作(イベント)に合わせてハ

ンドラが呼ばれる •  ハンドラがUIコンポーネントを

操作する •  次のイベント待ち

GUIプログラミングって? スクリプト •  シェルで起動 •  処理を逐次実行 •  最後の処理を終えたら終了 •  シェルに制御を戻す

Webフレームワーク •  Webサーバが起動 •  リクエストに合わせて

ハンドラが呼ばれる •  ハンドラがレスポンス返す •  次のリクエスト待ち

ディスパッチ

ハンドラ ビュー操作

イベントループ

Page 6: wxPython入門(大阪Pythonユーザの集まり2014/03)

ウィジェット Widget •  ボタンとかウィンドウとか、画面の構成要素 •  UIを実際に描画する(自動的にね) •  ユーザ操作に応じてイベントを生成する •  wxPython/wxWidget •  ほとんどのウィジェットがクラスで階層化

wxObject ... wxWindow

wxControl

wxBu4on

wxSta7cText

wxMenu

... wxPanel

wxTopLevelWindow wxFrame

wxDialog

Page 7: wxPython入門(大阪Pythonユーザの集まり2014/03)

イベントループ while len(event_q): ev = event_q.pop() handler = dispatch(ev) if handler: handler(ev)

イベントキュー

イベントループロジック

GUI操作 その他  イベント

イベントハンドラ

イベントハンドラ

イベントハンドラ

たいていのGUIフレームワークには、 ・イベントループを実行する関数 ・ハンドラを登録する関数 があり、(1)ハンドラを定義して(2)ディスパッチャに登録し、(3)イベントループを呼ぶようになっている。

Page 8: wxPython入門(大阪Pythonユーザの集まり2014/03)

wxPythonのイベントループ

>>> import wx >>> app = wx.App() # Appインスタンス >>> frame = wx.Frame(None) # ウィンドウつくる >>> app.SetTopWindow(frame) >>> frame.Show(True) True >>> app.MainLoop() # ここがメインループ (ループ中。ウィンドウ閉じるまで戻りませんw)

Page 9: wxPython入門(大阪Pythonユーザの集まり2014/03)

アプリは wxApp() をサブクラス化する

# coding: utf-8 import wx class MyApp(wx.App): def OnInit(self): #Appインスタンス生成時に呼び出される self.frame = wx.Frame(None) # 何でも属性にできる。Python最高ヤッホウ self.frame.SetTitle(u'よろしくwxさん') self.SetTopWindow(self.frame) self.frame.Show(True) return True # お約束。重要 if __name__=='__main__': app = MyApp() app.MainLoop()

Page 10: wxPython入門(大阪Pythonユーザの集まり2014/03)

GUIのレイアウト

Page 11: wxPython入門(大阪Pythonユーザの集まり2014/03)

GUIのレイアウト •  ウィジェットをウィンドウのどこに配置するか •  位置(x, y)と大きさ(w, h) •  配置(右寄せ、左寄せ) •  伸長(ウィンドウに合わせてサイズ変更) •  グループ化(OK/Cancelボタンとか)

•  wxWidgetsでは、Sizerオブジェクトがレイアウトを制御する •  ウィンドウにSizerを登録する

Page 12: wxPython入門(大阪Pythonユーザの集まり2014/03)

BoxSizer()があれば無問題! だと思う

硬派のSizer道(嘘)

Page 13: wxPython入門(大阪Pythonユーザの集まり2014/03)

BoxSizer

wx.BoxSizer(wx.VERTICAL) wx.BoxSizer(wx.HORIZONTAL) v_box = wx.BoxSizer(wx.VERTICAL) v_box.Add(widget_A) v_box.Add(widget_B) h_box = wx.BoxSizer(wx.HORIZONTAL) h_box.Add(v_box) h_box.Add(widget_C)

VERTICAL

HORIZONTAL

C A

B

Page 14: wxPython入門(大阪Pythonユーザの集まり2014/03)

BoxSizer def OnInit(self): ... label = wx.StaticText( self.frame, -1, u'ボタン三兄弟', style=wx.ALIGN_CENTER) btn_1 = wx.Button(self.frame, -1, u'ボタン長男') btn_2 = wx.Button(self.frame, -1, u'ボタン次男 ') btn_3 = wx.Button(self.frame, -1, u'ボタン三男') sizer = wx.BoxSizer(wx.VERTICAL) btn_sizer = wx.BoxSizer(wx.HORIZONTAL) btn_sizer.Add(btn_1, 3, wx.ALL|wx.EXPAND, 30) btn_sizer.Add(btn_2, 2, wx.ALL|wx.EXPAND, 20) btn_sizer.Add(btn_3, 1, wx.ALL|wx.EXPAND, 10) sizer.Add(label, 0, wx.ALL|wx.EXPAND, 50) sizer.Add(btn_sizer, 0, wx.ALL|wx.EXPAND, 0) self.frame.SetSizer(sizer) self.frame.Fit()

50

50

50 30 30

30

20

20 10

10 10

20 20

(上揃え)

Page 15: wxPython入門(大阪Pythonユーザの集まり2014/03)

イベント処理

Page 16: wxPython入門(大阪Pythonユーザの集まり2014/03)

おさらい

イベントキュー

イベントループロジック

GUI操作 その他  イベント

イベントハンドラ

イベントハンドラ

イベントハンドラ

GUIプログラム •  シェルで起動 •  操作(イベント)に合わせてハ

ンドラが呼ばれる •  ハンドラがUIコンポーネントを

操作する •  次のイベント待ち ディス

パッチ

ハンドラ ビュー操作

イベントループ

Page 17: wxPython入門(大阪Pythonユーザの集まり2014/03)

イベントハンドラはどこにでも書ける def event_handler(evt): #普通の関数 ... •  evt: イベントオブジェクト

•  イベントの種類 •  パラメタ(マウス座標など) •  その他、発生元のウィジェットなどの情報

•  wx.Appのメソッドとして書くのがいい •  evtから他の変数引っ張るの大変 •  詳しくはあとで

Page 18: wxPython入門(大阪Pythonユーザの集まり2014/03)

イベントをハンドラに結びつける ウィジェットの Bind() メソッドをつかう 例: widget.Bind(wx.EVT_MOUSE, handler) •  この操作で、 •  「widget」が •  「マウスイベント」を受け取ると •  handler を呼び出す

•  アプリのインスタンスメソッドの場合 •  def handler(self, evt): ... •  self.bind(wx.EVT_MOUSE, self.handler)

Page 19: wxPython入門(大阪Pythonユーザの集まり2014/03)

ハンドラの中でウィジェットを操作する def OnInit(self): ... self.btn = wx.Button(self.frame, -1, u'押さないで') self.btn.Bind(wx.EVT_BUTTON, self.OnOsunaPressed) def OnOsunaPressed(self, evt): self.btn.SetLabel(u'マジで'+self.btn.GetLabel()) self.frame.Fit()

Page 20: wxPython入門(大阪Pythonユーザの集まり2014/03)

tips

Page 21: wxPython入門(大阪Pythonユーザの集まり2014/03)

アプリケーションの参照をとる •  Appクラスに変数置いてるんだけど。

 →いいね! •  モジュール変数使って渡していい? ダメです

•  ウィンドウにAppのインスタンス渡す?ダメ

_人人人人人人人_ > wx.GetApp() < ‾Y^Y^Y^Y^Y^Y‾

実例はあとで

Page 22: wxPython入門(大阪Pythonユーザの集まり2014/03)

大きなアプリを書くとき •  ウィンドウやダイアログがたくさんある •  ウィンドウごとにモジュールを作る •  wx.Dialogやwx.Frameをサブクラス化する

ほとんどのウィジェットをPurePythonでサブクラス化OK! Python完全勝利(S)!

•  ウィンドウ固有の処理はモジュールに押し込める •  モジュールの if __module__==__main__ で wx.App を作ってアプリ化する

これで、画面構成モジュール単位で開発できます!

Page 23: wxPython入門(大阪Pythonユーザの集まり2014/03)

MVC的な住み分け •  Model

基本、Appインスタンス経由で参照する •  Controller

ビジネスロジックはAppインスタンス ウィジェットの細かい操作が多ければ 別モジュールに書く テストの住み分け的にも大事

•  View ウィジェットに複雑なデフォルト設定を したいなら別モジュールに書くのがいい

Page 24: wxPython入門(大阪Pythonユーザの集まり2014/03)

ダメじゃないけどダメな例 (app.py) def OnInit(self): self.main_window = wx.Frame(....) self.main_window_root_pane = wx.Panel(...) self.main_window_sizer = wx.BoxSizer(...) self.main_window_sizer.Add(self.main_window_root_pane) self.main_window.SetSizer(self.main_window_sizer) self.main_window_lr_sizer = wx.BoxSizer(...) self.main_window_left_pane = ... ....

Page 25: wxPython入門(大阪Pythonユーザの集まり2014/03)

いい感じな例 (app.py) import wx from main_window import MainWindow class MyApp(wx.App): def OnInit(self): self.main_window = MainWindow(...) self.SetTopWindow(self.main_window) ... self.main_window.some_method(...) ... return True def on_main_window_submit(self, evt): ...

(main_window.py) import wx class MainWindow(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) self.root_pane = wx.Panel(...) self.sizer = wx.BoxSizer(...) ... self.Bind(SOMEEVENT, self.app.on_main_window_submit) @property def app(self): return wx.GetApp() def some_method(self, ...) ... do something ...

こっちは  プレゼンテーション寄り

こっちは ビジネスロジック寄り  

Page 26: wxPython入門(大阪Pythonユーザの集まり2014/03)

いい感じな例 (app.py) import wx from main_window import MainWindow class MyApp(wx.App): def OnInit(self): self.main_window = MainWindow(...) self.SetTopWindow(self.main_window) ... self.main_window.some_method(...) ... return True def on_main_window_submit(self, evt): ...

(main_window.py) import wx class MainWindow(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) self.root_pane = wx.Panel(...) self.sizer = wx.BoxSizer(...) ... self.Bind(SOMEEVENT, self.app.on_main_window_submit) @property def app(self): return wx.GetApp() def some_method(self, ...) ... do something ...

こっちは  プレゼンテーション寄り

こっちは ビジネスロジック寄り  

Page 27: wxPython入門(大阪Pythonユーザの集まり2014/03)

モジュールのモック化 (main_window.py) import wx class MainWindow(wx.Frame): def __init__(self, *args, **kw): wx.Frame.__init__(self, *args, **kw) self.root_pane = wx.Panel(...) self.sizer = wx.BoxSizer(...) ... self.Bind(SOMEEVENT, self.app.on_main_window_submit) @property def app(self): return wx.GetApp() ...

(続く)

(続き) ... # ここからモック駆動用コード if __name__==‘__main__’: class DemoApp(wx.App): def on_main_window_submit(self, evt): ... do some nothing ... ... # モックアプリを作って走らせる app = DemoApp() w = MainWindow(None) app.main_window = w app.SetTopWindow(w) app.MainLoop() 「ホホウ。もう出来てるじゃないか。じゃあ工数減らして見積り再提出してもらえんかね」とお客様に言われても当方は一切責任をもちません

Page 28: wxPython入門(大阪Pythonユーザの集まり2014/03)

おすすめツール •  wxPython Demo

http://www.wxpython.org/download.php Demoを見れば大抵のウィジェットは使える

•  Dash https://itunes.apple.com/jp/app/dash-docs-snippets/id458034879?mt=12 wxWidets のドキュメントがあります

Page 29: wxPython入門(大阪Pythonユーザの集まり2014/03)

enjoy!