Python+Kivy入門

Kivyのイベント4: on_pressとカスタムイベントのPropagation(伝播) 

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

Kivyのビルトインイベントの一部は伝播の仕組みがありますが、on_press()on_release()は伝播の仕組みを持っていません。カスタムイベントも伝播することができますが、伝播の仕組み自体を完全に自作する必要があります。この記事ではこれらの伝播のサンプルコードを紹介します。

on_press()のPropagation

Kivyのイベントの仕組みや伝播の仕組み、カスタムイベントの作成方法については下記の記事をご覧ください。

on_press()の伝播のサンプルコードを紹介します。on_press()on_release()はそのイベント自体は伝播の仕組みを持っていないのでon_touch_*()のようには機能しないまでも、ボタンイベントとしての機能は持っているので各ウィジェットでイベントを呼び出せば伝播することができます。ただし、伝播の呼び出しと順番に関しては自分で制御する必要があります。

on_press()on_release()での伝播の実装は楽ではないので、特に必要ない場合はon_touch_*()で代用することをお勧めします。

サンプルコード

kivy_events_propagation4.py

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

class ParentWidget1(BoxLayout):
    def on_press(self,flg):
        if not flg == True:
            print(f'Pressed Parent 1')

class ParentWidget2(BoxLayout):
    def on_press(self, flg):
        print(f'Pressed Parent 2')
        self.parent.ids.button1.on_press() #手動でbutton1.on_pressを呼んでいる
        self.parent.on_press(True)
    
class ChildButton(Button):
    def on_press(self):
        print(f'Pressed Child: {self.text}')
        self.parent.on_press(False)

class KivyEventsPropagation4(App):
    def build(self):
        return ParentWidget1()

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

kivyeventspropagation4.kv

<ParentWidget1>:
    orientation: 'vertical'
    ChildButton:
        id: button1
        text: 'Child 1 button'
        font_size: 30

    ParentWidget2:
        ChildButton:
            text: 'Child 2 button'
            font_size: 30

コード解説

それぞれのウィジェットを継承したサブクラスでon_press()を定義することで、ボタンを押すとon_press()が呼ばれるようになっています。kvファイルではon_press()を呼び出していないことに注目してください。これはon_touch_*()の動作と同じです。

この例では下記の構成になっています。

<ParentWidget1>
+--- BoxLayout            # Parent1
+---+--- Button1          # Child1
+---+--- BoxLayout        # Parent1
+---+---+--- Button2      # Child2

このコードを実行すると下記のログがコンソールに出力されます。

# Button1を押したとき
Pressed Child: Child 1 button
Pressed Parent 1

# Button2を押したとき
Pressed Child: Child 2 button
Pressed Parent 2
Pressed Child: Child 1 button
Pressed Parent 1

本来のbubblingではButton1が押された時に全てのウィジェットが呼ばれるのが正しいと思うのですが、この例では順番の制御をしていないので、bubblingのようなものになっています。呼ばれる順番を変更したい場合は順番を制御する処理を追加する必要があります。

親のon_press()を呼ぶ

self.parent.on_press(False)

上記のコードでは親のon_press()を呼び出しています。引数にはフラグを渡しています。これをしない場合、下記のようにButton2を押したときにParent1が2回呼ばれてしまうので制御しています。

Pressed Child: Child 2 button
Pressed Parent 2
Pressed Child: Child 1 button
Pressed Parent 1
Pressed Parent 1

on_touch_*()ではreturn Trueにすれば伝播を止める事ができましたが、on_press()on_release()では自分で制御する必要があります。

この例でのフラグはParent1が呼ばれたかどうかを表していてTrueParent1が呼ばれたことを意味します。Parent1on_press()ではTrueの場合は処理をしません。これでParent1が2回呼ばれるのを防いでいます。

    def on_press(self,flg):
        if not flg == True:
            print(f'Pressed Parent 1')

ウィジェットが正しく伝播するようにする

Parent2on_press()ではChild1on_press()を呼び出しています。

self.parent.ids.button1.on_press()

この行を書かない場合は、下記のように伝播は正確に機能しません。

# Button1を押したとき
Pressed Child: Child 1 button
Pressed Parent 1

# Button2を押したとき
Pressed Child: Child 2 button
Pressed Parent 2

カスタムイベントのPropagation

次にカスタムイベントの伝播のサンプルコードを紹介します。カスタムイベントは伝播の仕組みを持っていないので完全に自分で実装する必要があります。これにはEventDispatcherクラスのregister_event_type()でカスタムイベントを登録し、dispatch()でイベントをトリガーし伝播を制御します。

サンプルコード

kivy_events_propagation5.py

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

class ParentWidget1(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type('on_click')
    
    def on_click(self):
        print(f'Pressed Parent 1')
    
    def trigger_click(self, phase, btn):
        self.dispatch("on_click")
        self.propagation_controller(phase, btn)

    def propagation_controller(self, phase, btn):
        parent2 = self.ids.get('parent2')
        button1 = self.ids.get('button1')
        button2 = self.ids.get('button2')

        if phase == 'capture':
            if btn == 'btn1':
                if button1:
                    button1.dispatch("on_click")
            elif btn == 'btn2':
                if parent2:
                    parent2.dispatch("on_click")
                if button1:
                    button1.dispatch("on_click")
                if button2:
                    button2.dispatch("on_click")
        elif phase == 'bubble':
            if btn == 'btn1':
                if parent2:
                    parent2.dispatch("on_click")
                if button2:
                    button2.dispatch("on_click")
                if button1:
                    button1.dispatch("on_click")
            elif btn == 'btn2':
                if button2:
                    button2.dispatch("on_click")

class ParentWidget2(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type('on_click')

    def on_click(self):
        print(f'Pressed Parent 2')

class ChildButton(Button):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type('on_click')

    def on_click(self):
        print(f'Pressed Child: {self.text}')

class KivyEventsPropagation5(App):
    def build(self):
        return ParentWidget1()

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

kivyeventspropagation5.kv

<ParentWidget1>:
    id: parent1
    orientation: 'vertical'
    ChildButton:
        id: button1
        text: 'Child 1 button'
        font_size: 30
        on_press:
            #root.trigger_click('capture', 'btn1')
            root.trigger_click('bubble', 'btn1')

    ParentWidget2:
        id: parent2
        ChildButton:
            id: button2
            text: 'Child 2 button'
            font_size: 30
            on_press:
                #root.trigger_click('capture', 'btn2')
                root.trigger_click('bubble', 'btn2')

コード解説

この例ではon_touch_*()と同じようにbubblingとcapturingの機能を再現しています。伝播を制御するpropagation_controllerクラスを定義し、イベントを手動で呼び出しています。この記述の順番を変えることによって自由に順番を入れ替えることができます。

コードの実行結果

BubbleフェーズかCaptureフェーズかを選択することができます。コードを実行するとコンソールには下記のように表示されます。

# Bubble phaseの場合
# Button1を押したとき
Pressed Parent 1
Pressed Parent 2
Pressed Child: Child 2 button
Pressed Child: Child 1 button

# Button2を押したとき
Pressed Parent 1
Pressed Parent 2
Pressed Child: Child 2 button
# Capture phaseの場合
# Button1を押したとき
Pressed Parent 1
Pressed Child: Child 1 button

# Button2を押したとき
Pressed Parent 1
Pressed Parent 2
Pressed Child: Child 1 button
Pressed Child: Child 2 button

カスタムイベントの登録

それぞれのサブクラスのコンストラクタ内では、register_event_type()でそのクラスのカスタムイベントのon_click()を登録しています。

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type('on_click')

    def on_click(self):
        print(f'Pressed Parent 1')

カスタムイベントのDispatch

trigger_click()はkvから呼び出すためのコールバック関数になります。on_click()のイベントをディスパッチします。また伝播を制御するpropagation_controllerメソッドを呼び出しています。

def trigger_click(self, phase, btn):
    self.dispatch("on_click")
    self.propagation_controller(phase, btn)

kvファイルではon_press()trigger_click()をバインドし、引数にphasebtnを渡しています。phasecapturebubbleを選択することができます。またpropagation_controller()で押されたボタンを判別するためにボタン名を渡しています。btn1Button1btn2がButton2になります。

on_press:
    #root.trigger_click('capture', 'btn1')
    root.trigger_click('bubble', 'btn1')

伝播を管理するpropagation_controller()

propagation_controller()では伝播の管理をしています。

下記ではget()を使用していウィジェットのオブジェクトを取得しています。

        parent2 = self.ids.get('parent2')
        button1 = self.ids.get('button1')
        button2 = self.ids.get('button2')

下記ではphase引数から渡されたcapturebubbleかを分岐処理しています。更にbtn引数から渡されたボタンによって処理を分岐しています。

if phase == 'capture':
    pass
elif phase == 'bubble':
    pass
if btn == 'btn1':
    pass
elif btn == 'btn2':
    pass

個別にdispatch()を呼ぶことでイベントを発生させる順番を変えることができます。

                if parent2:
                    parent2.dispatch("on_click")
                if button1:
                    button1.dispatch("on_click")
                if button2:
                    button2.dispatch("on_click")

Comment

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