Pythonスキルの習得

【 Python 】かっこいいGUI wxPythonの使い方入門 その5 – ボタン、メニュー選択等で発生する、イベントの処理方法 を紹介 –

【 Python 】かっこいいGUI wxPythonの使い方入門 その5 – ボタン、メニュー選択等で発生する、イベントの処理方法 を紹介 –

本記事は、

  • Pythonのスキル、初心者以上の方
  • wxPythonの、ウィジェットの設置方法、Sizerの使い方を知っている方

を対象にした内容になっております。

ウィジェットの設置方法、Sizerの使い方を知らない方は、前回までの記事で、ご紹介しております。

先にそちらを読まれてから、本記事を読まれる事をオススメします。


本記事を含む、一連の記事を読むことで、(全7回)

  • かっこよくて
  • おしゃれな

「簡単なGUIアプリ」を作成できるようになります。

例えば、wxPythonで、画像表示アプリを作ってみました。

上記みたいな、見た目(Macの場合)となります。

本記事は、下記の続編となります。

【 Python 】 かっこいいGUI wxPythonの使い方入門 その1
【 Python 】 かっこいいGUI wxPythonの使い方入門 その1Pythonの、かっこいいGUIである、wxPythonの使い方を紹介した記事です。...
【 Python 】 かっこいいGUI wxPythonの使い方入門 その2 - ウィジェットの紹介 -
【 Python 】 かっこいいGUI wxPythonの使い方入門 その2 - ウィジェットの紹介 -Pythonの、かっこいいGUIである、wxPythonの使い方を紹介した記事です。ウィジェットの紹介とコードによる配置方法を紹介しました。...
【 Python 】かっこいいGUI wxPythonの使い方入門 その3 - レイアウト( Panel , Sizer )の紹介 -
【 Python 】かっこいいGUI wxPythonの使い方入門 その3 - レイアウト( Panel , Sizer )の紹介 -Pythonの、かっこいいGUIである、wxPythonの使い方を紹介した記事です。wxPythonにおいて、レイアウト設計の考え方と設置方法をご紹介しました。パネルと、サイザー(wrapsizer、staticboxsizer、flexgridsizer、gridsizer、boxsizer)の概要説明と、Pythonによるコード紹介をした記事です。...
【 Python 】かっこいいGUI wxPythonの使い方入門 その4 – ステータスバー 、メニューバー の紹介 –
【 Python 】かっこいいGUI wxPythonの使い方入門 その4 – ステータスバー 、メニューバー の紹介 –Pythonの、かっこいいGUIである、wxPythonの使い方を紹介した記事です。wxPythonにおいて、ステータスバー、メニューバーの設置方法を、Pythonによるコードにて紹介した記事です。...

本記事のゴール設定

  • イベント発生時の処理方法を理解する(メニュー選択時も含む)
  • イベント処理の裏側で、何が起こっているかを理解する

としております。

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において、イベントは、下記イメージのように、処理されています。

  1. 各イベント(ボタンクリック等)は、アプリのイベントキューに格納される
  2. app.MainLoop()が、イベントキューから順番にイベントを取得して、発生しているウィジェットへディスパッチする。
  3. 発生しているウィジェットで、イベントが処理される。(イベント処理は、前の章で紹介した、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の紹介記事を、完了させようと思っています。

ここまで付き合って頂いた方々は、本当にお疲れ様です。

もう少しで完了なので、最後までお付き合い頂けますと幸いです。

次回は、ポップアップウィンドウ(ファイルダイアログ)の作り方になります。

【 Python 】かっこいいGUI wxPythonの使い方入門 その6 – ファイルダイアログの作り方 紹介 –
【 Python 】かっこいいGUI wxPythonの使い方入門 その6 – ファイルダイアログの作り方 紹介 –Pythonの、かっこいいGUIである、wxPythonの使い方を紹介した記事です。wxPythonにおいて、ファイルダイアログ ボックスの作り方を紹介した記事です。...

今回も、最後までお付き合い頂き、ありがとうございました。