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)
以下が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)
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_start
eventが一回 - 進行中に
on_progress
eventが複数回 - 終わった時に
on_complete
eventが一回
発生しているのが見て取れます。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'}
位置引数123
がButton 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文が使えないのが少し不便。