本記事は、
- Pythonのスキル、初心者以上の方
- wxPythonの、ウィジェットの設置方法、Sizerの使い方を知っている方
を対象にした内容になっております。
ウィジェットの設置方法、Sizerの使い方を知らない方は、前回までの記事で、ご紹介しております。
先にそちらを読まれてから、本記事を読まれる事をオススメします。
本記事を含む、一連の記事を読むことで、(全7回)
- かっこよくて
- おしゃれな
「簡単なGUIアプリ」を作成できるようになります。
例えば、wxPythonで、画像表示アプリを作ってみました。
上記みたいな、見た目(Macの場合)となります。
本記事は、下記の続編となります。
本記事のゴール設定
- イベント発生時の処理方法を理解する(メニュー選択時も含む)
- イベント処理の裏側で、何が起こっているかを理解する
としております。
wxPython イベント処理方法
実例を通して、体験してみる。
サンプルとして、ボタンをクリックしたら、
- テキストウィジェットに、「クリック!!」
と表示するアプリを作ってみます。
アプリの見た目
下記のように、テキスト、ボタンが1個ずつ配置された、アプリを例に解説します。
wxPython の雛形コード(おさらい)と追記する部分
前回までに、雛形コードとして、下記をご紹介しました。
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
super().__init__(self, parent)
######################################
# ここにSizer, ボタン等のウィジェットを記載
# (本記事にて、解説)
######################################
######################################################
# ここにボタンを押下等のイベント処理を、メソッドで記載することが多い
# (別途、解説します)
######################################################
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, id=-1, title='wxPython')
panel = MyPanel(self)
########################################################
# 必要ならば、メニューバー、ステータスバー、ツールバーを作成する
# (別途、解説します)
########################################################
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
この章で追記するコードは、
- ウィジェットの設置とイベント処理の登録 → 7〜10行目に該当
- 具体的なイベント処理の記載 → 12〜15行目に該当
部分になります。
ウィジェットの設置と、イベント処理のコード
ウィジェットの設置と、イベント処理を記載したコードが、下記になります。
(先ほど紹介した雛形コード中から、MyPanel 部分のみ、抜粋しております。)
class MyPanel(wx.Panel):
def __init__(self, parent):
super().__init__(self, parent)
# ウィジェットの設置
self.text = wx.TextCtrl(self, -1)
self.text.SetBackgroundColour('gray')
button = wx.Button(self, -1, 'Click')
box_sizer = wx.BoxSizer(wx.VERTICAL)
box_sizer.Add(self.text, 1, wx.EXPAND | wx.ALL, 10)
box_sizer.Add(button, 0, wx.ALIGN_CENTER | wx.ALL, 10)
self.SetSizer(box_sizer)
# イベント処理時、どのメソッドを呼び出すかを登録する
button.Bind(wx.EVT_BUTTON, self.OnButtonPress)
# イベント処理を記載
def OnButtonPress(self, event):
self.text.SetLabel('クリック')
ボタンをクリックしたら、「wx.EVT_BUTTON」イベントが発生します。
16行目の
- button.Bind(wx.EVT_BUTTON, self.OnButtonPress)
ですが、
- 「button」オブジェクトに
- 「wx.EVT_BUTTON」イベントが発生したら
- 「self.OnButtonPress」メソッドを実行させる
という、「関連付け」の設定をしています。
そして19行目に、イベント発生時に呼び出される、「self.OnButtonPress」メソッドを定義しています。(第2引数event を受取ります。)
上記の「self.OnButtonPress」メソッドのように、イベント発生時に呼び出されるメソッドを
- イベントハンドラ
と呼ばれます。
BIndメソッドは、
- オブジェクト(ボタン等)に、発生したイベントに対応する、イベントハンドラを関連付ける
という処理をしています。
<実行(ボタンクリック) 結果>
wxPython イベント処理の裏側
wxPyhonにおいて、イベントは、下記イメージのように、処理されています。
- 各イベント(ボタンクリック等)は、アプリのイベントキューに格納される
- app.MainLoop()が、イベントキューから順番にイベントを取得して、発生しているウィジェットへディスパッチする。
- 発生しているウィジェットで、イベントが処理される。(イベント処理は、前の章で紹介した、Bind( ) で登録する。)
イベントの伝搬
イベントの伝搬とは?
イベントは、子供から親に伝搬されていきます。
正確には
- 発生したウィジェットに、まずイベント発生が伝わる
- そこでイベント処理が実施されれば、本イベントは終了
- イベント処理されないと、親にイベントが伝番されていく。
下記イメージ絵をご覧下さい。
子供側でイベントが処理されると、親にはイベントは伝搬されませんが、子供側のイベント処理メソッド内で
- event.Skip( )
を使用すると、イベントが親に伝搬されます。
「event.Skip()」は下記のように、記載します。
# イベント処理を記載
def OnButtonPress(self, event):
self.text.SetLabel('クリック')
# イベントを親に伝搬する
event.Skip()
親側で、イベントの関連付け(Bind)を実施する場合
先ほどの事例では、
- button.Bind(wx.EVT_BUTTON, self.OnButtonPress)
というように、ウィジェットに、イベントに対応するイベントハンドラを関連付けていました。
この章では、その親である、「Panel」や「Frame」で、関連付けを実施する方法を見ていきます。
具体例としまして、
- イベントハンドラのメソッド名を、「OnButtonPressInPanel」
- イベント発生源である、Buttonオブジェクトを格納した変数名を、「button」
だったとします。
その場合、「Panel」クラスのコンストラクタ内で、
self.Bind(wx.EVT_BUTTON, self.OnButtonPressInPanel, button)のように、
- 第1引数:イベントの種類
- 第2引数:イベントハンドラ
- 第3引数:イベントの発生源(省略可能 デフォルト None)
と記載します。
この場合の「self」は、「Panel」を現しているので、「Panel」上で関連付けている事になります。
また、第3引数を省略した場合の挙動は、次の章でご紹介しています。
Panelクラスのコードは、以下の通りに、変更されます。
class MyPanel(wx.Panel):
def __init__(self, parent):
super().__init__(self, parent)
# ウィジェットの設置
self.text = wx.TextCtrl(self, -1)
self.text.SetBackgroundColour('gray')
button = wx.Button(self, -1, 'Click')
box_sizer = wx.BoxSizer(wx.VERTICAL)
box_sizer.Add(self.text, 1, wx.EXPAND | wx.ALL, 10)
box_sizer.Add(self.button, 0, wx.ALIGN_CENTER | wx.ALL, 10)
self.SetSizer(box_sizer)
# パネルオブジェクトに登録(イベントとイベントハンドラーのリンク)
self.Bind(wx.EVT_BUTTON, self.OnButtonPressInPanel, button)
# イベントハンドラー
def OnButtonPressInPanel(self, event):
self.text.SetLabel('Click')
# 親にイベントを伝搬したい場合
# event.Skip()
どういう場合、親側でイベントの関連付けを実施する?
例えば、下記のようにボタン3つ+テキストのアプリを作ったとします。
どのボタンを押しても、同じような処理をしたい場合があると思います。
こういった場合に、親側(ここでは、Panel側)で、関連付けを実施すると、便利です。
Pythonのコード紹介
前章でご紹介しました
- self.Bind(wx.EVT_BUTTON, self.OnButtonPressInPanel, button)
で第3引数(button)を、省略したコードを使って、ご紹介します。
省略した場合、self上(この場合、Panel)で発生した、ボタンクリックイベント(wx.EVT_BUTTON)は、全てこのイベントハンドラに、関連付けられます。
第3引数を省略した、関連付けコードは、以下の通りです。
# MyPanelクラスのコンストラクタ内で
# イベントハンドラを、「OnButtonPressInPanel」とした場合
self.Bind(wx.EVT_BUTTON, self.OnButtonPressInPanel)
イベントハンドラのコードですが、例えば、
- 「押下したボタンのラベル名」を、テキストウィジェットに表示させたい場合
イベントハンドラのコードは、以下の通りになります。
def OnButtonPressInPanel(self, event):
# イベント発生源の、オブジェクトを取得
clicked_button_object = event.GetEventObject()
# テキストウィジェットに、表示
self.text.SetLabel(clicked_button_object.GetLabel())
<実行結果>
メニューバーのイベント処理
実例を通して、体験してみる。
題材となるアプリ紹介
第4回の記事で作成した、下記アプリをもとに、機能を追加してみます。
第4回記事を読んでいなくても、理解できる内容になっています。
「ファイル」メニューの下に、
- 新規作成
- 保存
- 終了
がある。 その他にも、「編集」メニュー等もあり。
<アプリの見た目>
<Pythonのコード>
import wx
class MyPanel(wx.Panel):
### メニューイベント処理に関係ないため、MyPanelの中身は、省略 ###
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, id=-1, title='wxPython')
panel = MyPanel(self)
# メニュー作成
self.create_menu_bar()
self.Show()
def create_menu_bar(self):
f_menu = wx.Menu()
f_menu.Append(-1, '新規作成')
f_menu.Append(-1, '保存')
f_menu.Append(-1, '終了')
s_menu = wx.Menu()
s_menu.Append(-1, '画像サイズ変更')
s_menu.Append(-1, 'グレーイメージ化')
m_bar = wx.MenuBar()
m_bar.Append(f_menu, 'ファイル')
m_bar.Append(s_menu, '編集')
self.SetMenuBar(m_bar)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
追加する機能
- 「ファイル」メニュー中の、「終了」を選択すると、
- アプリを終了させる
処理を実例に、ご紹介します。
Pythonコードの紹介
追加、もしくは変更されるコードは、以下の通りです。
- 18行目: exit = f_menu.Append(-1, ‘終了’) と、メニューオブジェクトを、変数に格納
- 30行目:create_menu_bar(self)メソッドの最後部分に、Bind()を記載
- 33行目:イベントハンドラを追加
import wx
class MyPanel(wx.Panel):
### メニューに関係ないため、MyPanelの中身は、省略 ###
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, id=-1, title='wxPython')
panel = MyPanel(self)
# メニュー作成
self.create_menu_bar()
self.Show()
def create_menu_bar(self):
f_menu = wx.Menu()
f_menu.Append(-1, '新規作成')
f_menu.Append(-1, '保存')
exit = f_menu.Append(-1, '終了')
s_menu = wx.Menu()
s_menu.Append(-1, '画像サイズ変更')
s_menu.Append(-1, 'グレーイメージ化')
m_bar = wx.MenuBar()
m_bar.Append(f_menu, 'ファイル')
m_bar.Append(s_menu, '編集')
self.SetMenuBar(m_bar)
# メニューのイベントリンクの登録
self.Bind(wx.EVT_MENU, self.OnExit, exit)
# メニューのイベントハンドラ
def OnExit(self, event):
# 終了作業
self.Close()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
メニューを選択した場合、「wx.EVT_MENU」というイベントが発生します。
またこのイベントの関連付け作業は、Frame上で行われています。
次回予告
「ファイルを開く」ダイアログボックスの作り方をご紹介します。
また次回以降になると思いますが、
- ポップアップウィンドウ(ファイルダイアログ)の作り方
- exe(Windows)、dmg(Mac)ファイルにビルドする方法
をご紹介して、一連のwxPythonの紹介記事を、完了させようと思っています。
ここまで付き合って頂いた方々は、本当にお疲れ様です。
もう少しで完了なので、最後までお付き合い頂けますと幸いです。
次回は、ポップアップウィンドウ(ファイルダイアログ)の作り方になります。
今回も、最後までお付き合い頂き、ありがとうございました。