21
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kivyの基本部品 EventDispatcher

Last updated at Posted at 2016-10-18

EventDispatcher

EventDispatcherはKivyの基本的なeventの仕組みを実装している部品のようです。様々な部品(Widget,Animation,App,Sound等)がこのEventDispatcherを継承していて、「Buttonを押した時に何かをする」や「Animationが完了した時に何かする」、「アプリが中断された時に何かする」、「音が鳴り止んだ時に何かする」等のように何かをきっかけに処理を行う仕組みを用意してくれています。

bind()

それでは実際に使ってみます。eventと言えばButtonが定番だと思うのでまずはそれを使った例から。

from kivy.app import runTouchApp
from kivy.factory import Factory

button = Factory.Button(
    text='Press Me',
    font_size='80sp',
)

button.bind(on_press=lambda button: print('callback1'))
button.bind(on_press=lambda button: print('callback2'))

runTouchApp(button)

08_00.png

以下がButtonを一回押した時の出力で、渡した関数が渡した順とは逆に呼ばれているのが確認できます。

callback2
callback1

もう一つの例も見てみます。

from kivy.app import runTouchApp
from kivy.factory import Factory

button = Factory.Button(
    text='Press Me',
    font_size='80sp',
)
button.bind(
    on_press=lambda button: print('Buttonが押されました'),
    on_release=lambda button: print('Buttonが離されました'),
)

runTouchApp(button)
Buttonが押されました
Buttonが離されました

このように複数のeventに対してまとめて関数を渡す事もできます。またwidgetであれば以下のように初期化時にも渡せます。

from kivy.app import runTouchApp
from kivy.factory import Factory

runTouchApp(Factory.Button(
    text='Press Me',
    font_size='80sp',
    on_press=lambda button: print('Buttonが押されました'),
    on_release=lambda button: print('Buttonが離されました'),
))

dispatch()

上の例なのですが一々Buttonを押して動作確認するのが面倒くさいと感じたのなら以下のように書くことができます。

from kivy.factory import Factory

button = Factory.Button()
button.bind(
    on_press=lambda button: print('Buttonが押されました'),
    on_release=lambda button: print('Buttonが離されました'),
)
button.dispatch('on_press')
button.dispatch('on_release')
Buttonが押されました
Buttonが離されました

このように書くことで実際にButtonを押さずともeventを発生させられます。実際これはButtonが押された時に自ら呼んでいるmethodです。

unbind()

bind()で結びつけた関数はunbind()ほどく事ができます。

from kivy.factory import Factory

def callback2(button):
    print('callback2')

button = Factory.Button()
button.bind(on_press=lambda button: print('callback1'))
button.bind(on_press=callback2)
button.bind(on_press=lambda button: print('callback3'))

button.dispatch('on_press')
print('-----------------------------------')
button.unbind(on_press=callback2)
button.dispatch('on_press')
callback3
callback2
callback1
-----------------------------------
callback3
callback1

追加の引数

次は別のEventDispatcherであるAnimationも見てみます。

from kivy.app import runTouchApp
from kivy.factory import Factory
from kivy.animation import Animation

# このLabelをAnimationの対象とする
label = Factory.Label(text='Label', font_size='80sp')
# 徐々に透明にするAnimation
anim = Animation(opacity=0, step=0.1, duration=.5)

anim.bind(
    on_start=lambda anim, *args: print('on_start', *args),
    on_complete=lambda anim, *args: print('on_complete', *args),
    on_progress=lambda anim, *args: print('on_progress', *args),
)
anim.start(label)

runTouchApp(label)

08_01_00.png
08_01_01.png
08_01_02.png

on_start <kivy.uix.label.Label object at 0x7f2ce3e43388>
on_progress <kivy.uix.label.Label object at 0x7f2ce3e43388> 0.0
on_progress <kivy.uix.label.Label object at 0x7f2ce3e43388> 0.19305010401876643
on_progress <kivy.uix.label.Label object at 0x7f2ce3e43388> 0.38978709201910533
on_progress <kivy.uix.label.Label object at 0x7f2ce3e43388> 0.5864729700260796
on_progress <kivy.uix.label.Label object at 0x7f2ce3e43388> 0.7831993760191835
on_progress <kivy.uix.label.Label object at 0x7f2ce3e43388> 0.9799360600009095
on_progress <kivy.uix.label.Label object at 0x7f2ce3e43388> 1.0
on_complete <kivy.uix.label.Label object at 0x7f2ce3e43388>

Animationが

  • 始まった時にon_starteventが一回
  • 進行中にon_progresseventが複数回
  • 終わった時にon_completeeventが一回

発生しているのが見て取れます。Buttonのeventの時と違うのはこれら三つのeventが追加の引数を伴って発生している事です。三つともAnimationの対象となったLabelを伴ってますし、on_progressに於いてはさらにAnimationの進行度を割合で表した数値を伴ってます。このように各eventによって伴う引数が異なるのでcallback関数は可変長の引数を受け取れるようにしておくといいかもしれません。

default handlerを使ったやり方

今度は関数を結びつけるのとは別の方法も見ていきます。それはeventと同じ名前のmethodを上書きする方法です。EventDispatcherの一つであるAppを使う時はこの方法を使うことが多いと思います。

from kivy.app import App


class MyApp(App):
    # on_startというeventと同名のmethodを上書き
    def on_start(self):
        print('default handler')


app = MyApp()
app.bind(on_start=lambda button: print('callback'))  # 勿論bind()も可
app.bind(on_start=lambda button: print('callback2'))
app.run()
callback2
callback
default handler

戻り値の効果

戻り値にも触れておきます。次のコードを見てください。

from kivy.app import App


class MyApp(App):
    def on_start(self):
        print('default handler')

def callback(app):
    print('callback')
    return True

def callback2(app):
    print('callback2')
    # return True  A行

app = MyApp()
app.bind(on_start=callback)
app.bind(on_start=callback2)
print('戻り値', app.dispatch('on_start'))
# default handlerが呼ばれていない。
callback2
callback
戻り値 True

またA行を有効にすると出力は

# callback1とdefault handlerが呼ばれていない。
callback2
戻り値 True

となります。このように結びつけた関数が真を返すとそれ以降の関数が呼ばれなくなりdispatch()も真を返すようになります。(default handlerが真を返した場合もdispatch()は真を返します)。ほとんどのeventにおいてはこの事は意識しなくていいのですがon_touch_xxx系のeventではこの仕組みが重要なので触れておきました。

bind()の制限

以下のように同じ物を二度登録しようとしても

from kivy.factory import Factory


def callback(__):
    print('callback')


button = Factory.Button()
button.bind(on_press=callback)
button.bind(on_press=callback)
button.dispatch('on_press')
標準出力
callback

となります。bind()は同じ物を二度登録できない仕様なのです。なのでそのような事がしたいのなら次に示すより高機能版を使わないといけません。

fbind()unbind_uid()

from kivy.factory import Factory


def callback(__):
    print('callback')


button = Factory.Button()
uid1 = button.fbind('on_press', callback)
button.bind(on_press=lambda __: print('callback2'))
uid2 = button.fbind('on_press', callback)
button.dispatch('on_press')
print('-----------------')
button.unbind_uid('on_press', uid1)
button.dispatch('on_press')
print('-----------------')
button.unbind_uid('on_press', uid2)
button.dispatch('on_press')
callback
callback2
callback
-----------------
callback
callback2
-----------------
callback2

この高機能版の特徴としては他にも

  • 既定ではcallback関数への直接参照が結び付けられる。(bind()は条件付きの弱参照)
  • bind()より効率的
  • callback関数へ渡す引数も指定できる

があります。最後の特徴は分かりにくいと思うので次の例を見てください。

from kivy.factory import Factory


def callback(*args, **kwargs):
    print('callback', args, kwargs)


button = Factory.Button()
button.fbind('on_press', callback, 123, kivy='awesome')
button.dispatch('on_press')
callback (123, <kivy.uix.button.Button object at 0x7f5e4e01aa70>) {'kivy': 'awesome'}

位置引数123Button objectの前に来るのが注意点。

自分でeventを作ってみる

今度は使うだけではなく自分で作ってみます。

from kivy.event import EventDispatcher


class MyClass(EventDispatcher):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type('on_test')  # 1

    def on_test(self, *args, **kwargs):  # 2
        pass

作るには

  • eventの名前を決めて登録(名前は必ずon_から始まらなくてはいけない) (# 1)
  • default handlerを実装 (# 2)

するだけです。これでこれまでに出てきたeventと同じように扱えるようになっています。

a = MyClass()
a.bind(on_test=lambda __, *args, **kwargs: print('on_test', args, kwargs))

a.dispatch('on_test')
print('-------------------')
a.dispatch('on_test', 12, 34, kivy='awesome', python='awesome')
on_test () {}
-------------------
on_test (12, 34) {'kivy': 'awesome', 'python': 'awesome'}

簡単ですね。言い忘れてましたがdispatch()に追加の引数を渡すことでcallback関数にそれが渡ります。

__events__

eventを簡単に実装する方法としてこの__events__という物があります。例えば以下のMyClass

class MyClass(EventDispatcher):

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

    def on_test1(self, *args, **kwargs):
        pass

    def on_test2(self, *args, **kwargs):
        pass

__events__を使うことで以下のように簡潔に書けます。

class MyClass(EventDispatcher):

    __events__ = ('on_test1', 'on_test2', )

    def on_test1(self, *args, **kwargs):
        pass

    def on_test2(self, *args, **kwargs):
        pass

「最初からこの方法を紹介しろよ」と思うかもしれませんがこれはおそらく内部用の物なので使うか否かは自己責任で。私は使っています。

Kv言語におけるevent

Kv言語上では以下のように書くことでeventに処理を結びつけられます。

from kivy.lang import Builder


button = Builder.load_string('''
Button:
    on_press: print('callback1')
    on_press:  # 複数の文も書ける
        msg = 'callback2'
        print(msg)
''')
button.dispatch('on_press')
callback2
callback1

Kv言語で書く場合はreturn文が使えないのが少し不便。

21
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?