Python+Kivy入門

Kivyのイベント1:on_touch_downの正しい書き方とイベントの仕組み

この記事は約15分で読めます。

Kivyのイベントの仕組みとKivyが提供しているイベントハンドラの使い方を紹介します。この記事ではon_touch_downの書き方を例に解説しています。Kivyでは標準でいくつかのイベント関数が用意されていますが、独自のカスタムイベントを作ることも可能です。

Kivyのイベントの種類

Kivyのイベントは大きく分けると3つの種類に分類されます。この記事ではKivyが提供している標準イベントハンドラの使い方について説明します。

  • Kivy標準イベント
  • カスタムイベント
  • Clock()Property()イベント

Kivyの標準イベント

Kivy標準イベントハンドラの殆どは”on_”から始まる接頭辞の付いた関数です。これらのイベントハンドラはウィジェットに標準で組み込まれていて、on_press()on_release()などいくつかのウィジェットで共通で使えるイベントハンドラと、そのウィジェット特有のイベントハンドラがあります。またウィジェットに関連されたイベントハンドラの他にWindowクラスのイベントハンドラも多数用意されています。ほとんどの場合はこれらのKivy標準イベントと自作コールバック関数を組み合わせることでイベント処理は成り立ちます。以下にイベントハンドラの一部を列挙します。

  • on_press() : マウスクリックが押された時に呼び出されるイベント。
  • on_release() : クリックが離されるときに呼び出されるイベント。
  • on_touch_down() : マウスクリックやタッチ入力が画面に触れたときに呼び出されるイベント。
  • on_touch_move() : タッチやドラッグ操作が動いたときに呼び出されるイベント。
  • on_touch_up() : タッチ操作が離れたときに呼び出されるイベント。
  • on_size() : ウィジェットのサイズが変更されたときに呼び出されるイベント。
  • on_kv_post() : kv言語のロード後に呼び出されるイベント。

カスタムイベント

カスタムイベントは独自にイベント関数を作成して登録し、イベントをDispatch(イベントの発生を管理)することができます。またbind()fbind()を使用してコールバック関数を紐づけることも可能です。

ClockやPropertyなどのイベント

Clockイベント

KivyのClockイベントは一定の時間間隔で関数を呼び出したい場合や、一定時間経過後に関数を呼び出した場合など、時間で管理したい関数を扱うときに便利です。

Propertyイベント

Propertyクラスはデータを扱うイベントです。バインドしたPropertyの値が更新されるとKivyのオブザーバー設計により自動的に値が更新されます。これは主にpythonコードで書く場合に使用されます。

Kivyのイベントの仕組み

KivyのイベントはEventDispatcherクラスによって管理されています。EventDispatcher()はイベントの登録、バインド、イベントの発生を検知し、関連するコールバック関数を実行します。

ここでイベントに関連する用語を整理しておきましょう。

  • Dispatch : 待機中のイベントを監視し、イベントハンドラーや対応するコールバック関数の実行を許可する役割を持っています。Kivyではイベントを発生させることを指してる場合があります。
  • Event Listeners: イベントの発生を監視する役割を持っています。
  • Event Handlers: イベントが発生したときに実行される処理を記述する関数やメソッドです。
  • Callback functions :ある 関数から他の関数を呼び出す場合や他の関数に引数として渡される関数のことを指します。例えばA関数からB関数を呼び出す。または、A関数の引数としてB関数を指定する。これらの例ではB関数がコールバック関数にあたります。
  • bind : イベントハンドラーとコールバック関数の紐づけを行います。
KivyのEvent Dispatcherフロー

Kivyで例えると、ユーザーがボタンをクリックするとイベントリスナーによって監視されていたイベントがトリガーされ、Kivy標準イベントハンドラーのon_press()が呼ばれます。そして、コールバック関数としてバインドしていたon_callback()が呼ばれます。これらはEvent Dispatcher()で管理されています。on_press()Buttonのイベントで、ボタンが押された瞬間にトリガーするイベントです。ボタンが押されたらコールバック関数のon_callback()を呼び出すといった形になります。

Kivyイベントのライフサイクル

Kivy全体のイベントのライフサイクルをみてみましょう。Kivyアプリケーションを起動するとkvファイルの読み込みやウィジェットツリーの構築が行われるとイベントを待機するメインループが開始され、このループはアプリケーションが終了されるまで続きます、ユーザーのクリックやタップなどの動作が発生するとイベントを監視するEvent Dispatcher()がイベントの発生を検知し、イベントリスナーを呼び出します。次にイベントに関連したイベントハンドラとコールバック関数を呼び出し実行します。次にWindowクラスのイベントがある場合はそれを実行します。Clockクラスは別のループになっています。

Kivyイベントのライフサイクル

イベントの書き方

Kivy標準イベントハンドラの書き方を説明します。イベントの書き方はPythonコードで書く場合とkvコードで書く場合では書き方が異なります。

Pythonコードのイベントの書き方

この例では、Kivy標準のイベントハンドラを使用する方法とKivy標準イベントハンドラに加えてコールバック関数をバインドして呼び出す方法の2種類のイベントの使い方を書いています。

kivy_events1.py

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout

class MyLabel(Label):
    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            self.text = "Label 2 touched!"
            return True

class MyApp(App):
    def build(self):
        layout = BoxLayout(orientation='vertical')
        
        self.label1 = Label(text="Label 1 : Click Button.")
        self.label2 = MyLabel(text="Label 2 : Pressing this label triggers a touch event.")

        button = Button(text="Click here")
        button.bind(on_press=self.on_button_press)

        layout.add_widget(self.label1)
        layout.add_widget(self.label2)
        layout.add_widget(button)
        
        return layout

    def on_button_press(self, instance):
        self.label1.text = "Button pressed!"
        self.label2.text= instance.text

if __name__ == '__main__':
    MyApp().run()

Pythonコード解説

このサンプルでは、ButtonをクリックするとLabel1のtextを変更し、Label2にButtontextを表示します。また、Label2をクリックするとLabel2のtextを変更します。

<RootWidget>
+--- Label
+--- Label
+--- Button

Kivy標準イベントのon_touch_downの書き方

下記のコードはon_touch_down()の正確な書き方を示しています。on_touch_down()は伝播の仕組みを持っているので2つのreturnを書くことで伝播を制御します。

class MyLabel(Label):
    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            self.text = "Label 2 touched!"
            return True
        return super().on_touch_down(touch)

伝播についての詳細は下記の記事をご覧ください。

伝播を使用しないのであれば、下記のように書けば伝播されません。また、ifreturn Trueは書かなくても実行することができます。

class MyLabel(Label):
    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            self.text = "Label 2 touched!"
            return True

Kivy標準イベントハンドラを使用する場合はイベントハンドラをオーバーライドします。この例ではLabel2でon_touch_down()を使用するためにLabelを継承しクラスを別に定義し、on_touch_down()をオーバーライドして、処理を追加しています。

def on_touch_down(self, touch):

パラメーターのtouchは親ウィジェットの座標が渡されます。

if self.collide_point(*touch.pos):

collide_point()はタッチポイント (x, y) がウィジェットの領域内にあるかどうかをチェックします。touch.posはタッチの位置を示す座標です。つまり、タッチの位置がLabel2の領域内にあるかどうかをチェックしています。

self.text = "Label 2 touched!"

ここではLabel2のtextを変更する処理を追加しています。

return True

この行はKivyイベントの伝播の仕組みに使うためのフラグです。

Kivy標準イベント+コールバック関数をbindする方法

Kivy標準イベントハンドラにコールバック関数をバインドして呼び出す方法を解説します。

# ボタンを定義し、on_pressイベントハンドラとコールバック関数をバインドしています。
button = Button(text="Click here")
button.bind(on_press=self.on_button_press)

Kivy標準イベントハンドラとコールバック関数を紐づけるにはbind()を使用します。

# bind書き方
instance_name.bind(event_name = callback_function)

この例ではevent_nameにKivy標準イベントのon_press()を指定し、callback_functionに関数のon_button_press()を指定しています。

    def on_button_press(self, instance):
        self.label1.text = "Button pressed!"
        self.label2.text= instance.text

コールバック関数の処理になります。instanceにはイベントをトリガーしたウィジェットが渡されるため、どのウィジェットからイベントが発生したかを特定することができます。この例ではButtonのインスタンスが渡されます。例えば、複数のボタンが同じイベントハンドラを使用している場合、instanceを使用してどのボタンが押されたかを判断するのに役立ちます。

kv言語のイベントの書き方

kv言語で書く場合はかなり簡単にイベント関数を呼び出すことができます。bind()は明示的に行う必要はありません。同じツリー内のウィジェットを参照するだけの簡単な処理の場合はkv言語で記述した方が良い場合があります。しかし、複雑な多くの処理をしたい場合は向いていません。

このサンプルでは前章のPythonコードと同じ処理が実行されますが、kv言語ではreturnが使えないので伝播の制御をしたい場合はPythonコードで記述します。

kivy_events2.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout

class RootWidget(BoxLayout):
    def on_button_press(self, text):
        self.ids.label1.text = "Button pressed!"
        self.ids.label2.text = text

class KivyEvents2(App):
    def build(self):
        return RootWidget()

if __name__ == '__main__':
    KivyEvents2().run()

kivyevents2.kv

<RootWidget>:
    orientation: 'vertical'
    Label:
        id: label1
        text: 'Label 1 : Click Button.'
    Label:
        id: label2
        text: 'Label 2 : Pressing this label triggers a touch event.'
        on_touch_down: 
            if self.collide_point(*args[1].pos): self.text = "Label 2 touched!"
        #on_touch_down: self.text = "Label 2 touched!" # This is also viable.
    Button:
        text: 'Click here'
        on_press: root.on_button_press(self.text)

kvコード解説

kv言語でKivy標準イベントハンドラを使うには、イベントを使いたいウィジェットのプロパティにイベント名を指定します。

event_name: processing

コールバック関数を呼び出したい場合は下記のようになります。

event_name: keyword.callback_function

Kivy標準イベントのon_touch_downの使い方
on_touch_down: 
    if self.collide_point(*args[1].pos): self.text = "Label 2 touched!"

簡単な処理であればkv言語で書くことも可能です。if文やfor文も使用できます。この例ではPythonコードと同じようにcollide_point()を使用してタッチポイント (x, y) がウィジェットの領域内にあるかどうかをチェックしています。

args[1]はタプルで指定する書き方になります。on_touch_down()に渡される引数は通常selftouchの2つです。これらの引数はargsとして参照することができます。args[0]selfを指し、args[1]touchを指しています

self.text = "Label 2 touched!"

#idで指定する場合
label2.text = "Label 2 touched!"

self.textでは自身のLabel2のtextを変更するのでselfキーワードになります。今回はidを設定しているのでid_name.textとしても書けます。

下記のように簡潔に書くこともできます。

on_touch_down: self.text = "Label 2 touched!" # This is also viable.

Kivy標準イベント+コールバック関数をbindする方法
on_press: root.on_button_press(self.text)

kv言語でコールバック関数をバインドするにはkeyword.callback_functionを記述します。このように書けばKivyによって自動的にバインドされます。引数にself.textを指定し、自身であるButtontextを渡しています。

    def on_button_press(self, text):
        self.ids.label1.text = "Button pressed!"
        self.ids.label2.text = text

Pythonコード側で書いたコールバック関数になります。引数textでkv言語側から渡された引数を受け取って変更しています。

キーワードやidについての詳細は下記の記事も合わせてご覧ください。

kv言語のチェーン処理

kv言語でイベント記述する際に役に立つのがセミコロンを使用したチェーン処理になります。セミコロンで区切った順番で処理が実行されます。

processing1; processing2; processing3…

on_touch_down:
    if self.collide_point(*args[1].pos): self.text = "Label 2 touched!"; app.on_label_touch(self, *args)

コードが長くなって見づらい場合は、セミコロンの代わりに改行を使うこともできます。ただし、ifforなどの制御構文の途中で改行するとエラーになるので注意してください。

processing1
processing2
processing3

on_touch_down:
    if self.collide_point(*args[1].pos): self.text = "Label 2 touched!"
    app.on_label_touch(self, *args)

Comment

タイトルとURLをコピーしました