wxPython入門 増田 (@whosaysni)
自己紹介 • 増田 泰 (@whosaysni) • Google+ つかってません • GitHub<<<BitBucket
• 某バイオベンチャー勤務 • ドキュメント翻訳とかDjango本とか • 一般社団法人PyConJP理事 • PyConJPきてください 9/13-15 • http://2014.pycon.jp/
wxPython • GUIライブラリ • wxWidgetsのPythonラッパー • SWIG使ってる • 各OSのGUIコンポーネントを使う • OSXならCocoa • Windowsならwin32 • LinuxならGTK
• wxWidgetsライセンス • 条文はLGPL2だが、バイナリの配布時に独自の使用許諾ができる例外条項がある
まずは基本
GUIプログラム • シェルで起動 • 操作(イベント)に合わせてハ
ンドラが呼ばれる • ハンドラがUIコンポーネントを
操作する • 次のイベント待ち
GUIプログラミングって? スクリプト • シェルで起動 • 処理を逐次実行 • 最後の処理を終えたら終了 • シェルに制御を戻す
Webフレームワーク • Webサーバが起動 • リクエストに合わせて
ハンドラが呼ばれる • ハンドラがレスポンス返す • 次のリクエスト待ち
ディスパッチ
ハンドラ ビュー操作
イベントループ
ウィジェット Widget • ボタンとかウィンドウとか、画面の構成要素 • UIを実際に描画する(自動的にね) • ユーザ操作に応じてイベントを生成する • wxPython/wxWidget • ほとんどのウィジェットがクラスで階層化
wxObject ... wxWindow
wxControl
wxBu4on
wxSta7cText
wxMenu
... wxPanel
wxTopLevelWindow wxFrame
wxDialog
イベントループ while len(event_q): ev = event_q.pop() handler = dispatch(ev) if handler: handler(ev)
イベントキュー
イベントループロジック
GUI操作 その他 イベント
イベントハンドラ
イベントハンドラ
イベントハンドラ
たいていのGUIフレームワークには、 ・イベントループを実行する関数 ・ハンドラを登録する関数 があり、(1)ハンドラを定義して(2)ディスパッチャに登録し、(3)イベントループを呼ぶようになっている。
wxPythonのイベントループ
>>> import wx >>> app = wx.App() # Appインスタンス >>> frame = wx.Frame(None) # ウィンドウつくる >>> app.SetTopWindow(frame) >>> frame.Show(True) True >>> app.MainLoop() # ここがメインループ (ループ中。ウィンドウ閉じるまで戻りませんw)
アプリは 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()
GUIのレイアウト
GUIのレイアウト • ウィジェットをウィンドウのどこに配置するか • 位置(x, y)と大きさ(w, h) • 配置(右寄せ、左寄せ) • 伸長(ウィンドウに合わせてサイズ変更) • グループ化(OK/Cancelボタンとか)
• wxWidgetsでは、Sizerオブジェクトがレイアウトを制御する • ウィンドウにSizerを登録する
BoxSizer()があれば無問題! だと思う
硬派のSizer道(嘘)
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
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
(上揃え)
イベント処理
おさらい
イベントキュー
イベントループロジック
GUI操作 その他 イベント
イベントハンドラ
イベントハンドラ
イベントハンドラ
GUIプログラム • シェルで起動 • 操作(イベント)に合わせてハ
ンドラが呼ばれる • ハンドラがUIコンポーネントを
操作する • 次のイベント待ち ディス
パッチ
ハンドラ ビュー操作
イベントループ
イベントハンドラはどこにでも書ける def event_handler(evt): #普通の関数 ... • evt: イベントオブジェクト
• イベントの種類 • パラメタ(マウス座標など) • その他、発生元のウィジェットなどの情報
• wx.Appのメソッドとして書くのがいい • evtから他の変数引っ張るの大変 • 詳しくはあとで
イベントをハンドラに結びつける ウィジェットの Bind() メソッドをつかう 例: widget.Bind(wx.EVT_MOUSE, handler) • この操作で、 • 「widget」が • 「マウスイベント」を受け取ると • handler を呼び出す
• アプリのインスタンスメソッドの場合 • def handler(self, evt): ... • self.bind(wx.EVT_MOUSE, self.handler)
ハンドラの中でウィジェットを操作する 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()
tips
アプリケーションの参照をとる • Appクラスに変数置いてるんだけど。
→いいね! • モジュール変数使って渡していい? ダメです
• ウィンドウにAppのインスタンス渡す?ダメ
_人人人人人人人_ > wx.GetApp() < ‾Y^Y^Y^Y^Y^Y‾
実例はあとで
大きなアプリを書くとき • ウィンドウやダイアログがたくさんある • ウィンドウごとにモジュールを作る • wx.Dialogやwx.Frameをサブクラス化する
ほとんどのウィジェットをPurePythonでサブクラス化OK! Python完全勝利(S)!
• ウィンドウ固有の処理はモジュールに押し込める • モジュールの if __module__==__main__ で wx.App を作ってアプリ化する
これで、画面構成モジュール単位で開発できます!
MVC的な住み分け • Model
基本、Appインスタンス経由で参照する • Controller
ビジネスロジックはAppインスタンス ウィジェットの細かい操作が多ければ 別モジュールに書く テストの住み分け的にも大事
• View ウィジェットに複雑なデフォルト設定を したいなら別モジュールに書くのがいい
ダメじゃないけどダメな例 (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 = ... ....
いい感じな例 (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 ...
こっちは プレゼンテーション寄り
こっちは ビジネスロジック寄り
いい感じな例 (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 ...
こっちは プレゼンテーション寄り
こっちは ビジネスロジック寄り
モジュールのモック化 (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() 「ホホウ。もう出来てるじゃないか。じゃあ工数減らして見積り再提出してもらえんかね」とお客様に言われても当方は一切責任をもちません
おすすめツール • wxPython Demo
http://www.wxpython.org/download.php Demoを見れば大抵のウィジェットは使える
• Dash https://itunes.apple.com/jp/app/dash-docs-snippets/id458034879?mt=12 wxWidets のドキュメントがあります
enjoy!
Top Related