Quick search

Events and Properties (翻訳済み)

Event (イベント) は Kivy プログラミングの重要な部分の1つです。GUI開発の経験がある人にとっては驚くことではないかもしれませんが、初めての人にとっては重要なコンセプトです。イベントがどのように働き、それらをどうやってバインド (紐付け) するかを一度理解できれば、Kivy の至る所に Event が使用されていることがわかるでしょう。 Kivy で組み込みたいあらゆる動作は、Event を用いることで簡単に実現できます。

次の図は、EventがKivyフレームワークの中でどのように使用されているかを示したものです。

../_images/Events.png

Introduction to the Event Dispatcher (イベント・ディスパッチャ入門)

Kivyフレームワークの中で最も重要な基底クラスの1つが、EventDispatcher (イベント・ディスパッチャ) です。このクラスを用いることにより、イベントタイプを登録し、またそれらを関心ある集まり (普通は他のEventDispatcherです)”にディスパッチ (送り出す) できます。たとえば、WidgetAnimationClock などがEventDispatcherの例です。

EventDispatcherのオブジェクトは、Main Loop (メイン・ループ) にしたがって、Event を生成して操作します。

Main loop (メイン・ループ)

上に示した図のように、Kivyは Main Loop を持ちます。このループはアプリが動いている間ずっとまわり続け、それが停まるのはアプリが終了するときのみです。

Main Loop の内部では、毎回の反復において、Event が生成されます。Event はユーザの入力、ハードウェアのセンサ、その他のソースに対して生成されます。また、フレームがディスプレイにレンダリングされます。

アプリの中で callback (コールバック, これについては後で詳しく述べます) を規定するでしょう。この callback は Main Loop に呼び出されます。 もし callback があまりに長い時間を要し、停止しようとしなければ、Main Loop は壊れ、アプリはそれ以上適切に動作しません。”

Kivyで作るアプリの中では、長時間または無限のループやスリープを避けなければなりません。たとえば次のコードでは、その忌避すべき両方のことを行っています:

while True:
    animate_something()
    time.sleep(.10)

このコードを走らせると、プログラムは決してループを抜けません。それによりKivyは、本来なすべき他のすべてのことができなくなってしまうのです。その結果、あなたは何もインタラクトできない、真っ黒なウィンドウを目撃することになるでしょう。これを防ぐには、上記の animate_something() 関数が繰り返し呼び出されるように “schedule” (スケジュール) する必要があります。

Scheduling a repetitive event (繰り返しイベントをスケジュールする)

schedule_interval() を使えば、関数あるいはメソッドを1秒間あたりX回呼び出す、といったことが可能になります。以下は my_callback という関数を、1/30秒ごとに1回呼び出すという例です:

def my_callback(dt):
    print 'My callback is called', dt
event = Clock.schedule_interval(my_callback, 1 / 30.)

以前スケジュールされたイベントのスケジュールを解除するには複数の方法があります。 1つは、 cancel() または unschedule() を使用することです。

event.cancel()

もしくは:

Clock.unschedule(event)

またコールバックでFalseを返すこともできます。イベントは自動的にスケジュール解除になります:

count = 0
def my_callback(dt):
    global count
    count += 1
    if count == 10:
        print 'Last call of my callback, bye bye !'
        return False
    print 'My callback is called'
Clock.schedule_interval(my_callback, 1 / 30.)

Scheduling a one-time event (1回切りのイベント)

Using schedule_once(), you can call a function “later”, like in the next frame, or in X seconds:

def my_callback(dt):
    print 'My callback is called !'
Clock.schedule_once(my_callback, 1)

このスケジュールは、 my_callback を1秒後に呼び出します。2つ目の引数で、関数を呼び出すまでの時間を秒単位で指定します。2つ目の引数については、特別な値を与えることによって、以下のように動作させることもできます。

  • もし X>0 ならば、callbackはX秒後に呼び出される (上記の通り)

  • もし X=0 ならば、callbackは次のフレームの「後で」呼び出される

  • もし X=-1 ならば、callbackは次のフレームの「前に」呼び出される

すでにスケジュールされたイベントの中にいるとき、あるいは次のフレームが発生する前にイベントをスケジュールしたいとき、ほとんどの場合-1が用いられます。

A second method for repeating a function call is to first schedule a callback once with schedule_once(), and a second call to this function inside the callback itself:

def my_callback(dt):
    print 'My callback is called !'
    Clock.schedule_once(my_callback, 1)
Clock.schedule_once(my_callback, 1)

Main loop は、要求通りにスケジュールが実行されるようにつとめますが、スケジュールされたコールバックが実際にいつ呼び出されるかは、多少不確かです。ときには他のコールバックやタスクが予定より長くかかってしまい、実行のタイミングが少し遅れることもあるでしょう。

In the latter solution to the repetitive callback problem, the next iteration will be called at least one second after the last iteration ends. With schedule_interval() however, the callback is called every second.

Trigger events (Trigger を用いてイベントをスケジュールする)

場合によっては次のフレームに対して一度だけ呼び出されるように関数をスケジュールし、重複呼び出しを防止できます。それを達成するように誘惑されるかもしれません:

# First, schedule once.
event = Clock.schedule_once(my_callback, 0)

# Then, in another place you will have to unschedule first
# to avoid duplicate call. Then you can schedule again.
Clock.unschedule(event)
event = Clock.schedule_once(my_callback, 0)

トリガをプログラミングするこの方法は、イベントが既に完了していても、unscheduleを常に呼び出すためコストがかかります。さらに、毎回新しいイベントが作成されます。代わりにトリガー を使用してください::

trigger = Clock.create_trigger(my_callback)
# later
trigger()

trigger() を呼び出すたび、あなたの callback は1回だけスケジュールされるでしょう。もし既にスケジュール済であれば、再度スケジュールされることはありません。

Widget events (Widgetに関するイベント)

Widget に関するイベントには、デフォルトで2つのタイプがあります:

  • Property event (プロパティ・イベント): ウィジェットの位置やサイズなどの「プロパティ」が変わったときに発生するイベントを指します。

  • Widget-defined event (ウィジェットに定義されたイベント): たとえば Button が押されたときや離されたときに発生するようなイベントを指します。

widgetのタッチイベントがどのように管理され、伝播されるかの議論については、Widget touch event bubbling を参照してください。

Creating custom events (独自イベントの作成)

独自イベントを持つ EventDispatcher を作るには、クラス内でそのイベントの名前を登録し、同じ名前を持つメソッドを定義する必要があります。

次の例を参照してください:

class MyEventDispatcher(EventDispatcher):
    def __init__(self, **kwargs):
        self.register_event_type('on_test')
        super(MyEventDispatcher, self).__init__(**kwargs)

    def do_something(self, value):
        # when do_something is called, the 'on_test' event will be
        # dispatched with the value
        self.dispatch('on_test', value)

    def on_test(self, *args):
        print "I am dispatched", args

Attaching callbacks (コールバック関数を紐付けする)

イベントを使うには、コールバック関数を紐付けする必要があります。イベントがディスパッチされると、紐付けされたコールバック関数が、イベントに関連したパラメータとともに呼びされます。

コールバックにはあらゆる callable (呼び出し可能なオブジェクト) を用いることができますが、そのコールバックが、イベントの発する引数を確実に受け付けるようにしなければなりません。このため、普通は *args 引数を受け付けるようにするのが最も安全です。この引数は args リストの中にすべての引数をキャッチできます。

たとえば:

def my_callback(value, *args):
    print "Hello, I got an event!", args


ev = MyEventDispatcher()
ev.bind(on_test=my_callback)
ev.do_something('test')

コールバックの紐付けについては、 より多くの例が kivy.event.EventDispatcher.bind() にあります。

Introduction to Properties (プロパティ入門)

Property (プロパティ) は、イベント、およびそのイベントへの紐付けを定義するための素晴らしい手段です。基本的には、プロパティはイベントを生成します。どのようなイベントかというと、オブジェクトの属性に変更 (値の代入など) があったとき、その属性にリファレンスを持つすべてのプロパティも、自動的に更新されるようなイベントです。

異なる種類のプロパティがあり、それらを用いて、取り扱うデータの種類を記述することになります。

Declaration of a Property (プロパティの宣言)

プロパティを宣言するには、クラスレベルで行わなければなりません。そうすると、オブジェクトが作られる際に、(プロパティと紐付けされた) 実際の属性がインスタンス化されます。プロパティは属性ではありません: プロパティは、属性に基づいてイベントを生成するメカニズムなのです:

class MyWidget(Widget):

    text = StringProperty('')

__init__ をオーバーライドするときは、常に **kwargs を引数として受け付けるようにし、super() を用いて親クラスの __init__ を呼び出し、あなたのクラスインスタンスを渡すようにしてください。

def __init__(self, **kwargs):
    super(MyWidget, self).__init__(**kwargs)

Dispatching a Property event (プロパティイベントをディスパッチする)”

Kivyプロパティは、デフォルトで、 on_<property_name> のイベントを提供します。このイベントは、プロパティの値が変更されたときに呼び出されます。

注釈

ただし、プロパティに新しく与えた値が、元の値と変わらない場合は、 on_<property_name> のイベントは呼び出されません。

たとえば、次のコードを見てください:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 class CustomBtn(Widget):

     pressed = ListProperty([0, 0])

     def on_touch_down(self, touch):
         if self.collide_point(*touch.pos):
             self.pressed = touch.pos
             return True
         return super(CustomBtn, self).on_touch_down(touch)

     def on_pressed(self, instance, pos):
         print ('pressed at {pos}'.format(pos=pos))

上のコードの3行目は:

pressed = ListProperty([0, 0])

:class:`~kivy.properties.ListProperty の`pressed` プロパティを定義し、デフォルト値として`[0,0]`を与えています。これより先、このプロパティの値が変更されたときはいつでも、 on_pressed イベントが呼び出されるのです。

5行目では:

def on_touch_down(self, touch):
    if self.collide_point(*touch.pos):
        self.pressed = touch.pos
        return True
    return super(CustomBtn, self).on_touch_down(touch)

Widget クラスの on_touch_down() メソッドをオーバーライドしています。ここでは、touch がウィジェットの上でなされたかどうかをチェックしています。

もしタッチがウィジェット内部でなされた場合は、pressed の値をtouch.pos に変更し、True を返します。True を返すことによって、touchを「使い切り」、それ以上伝播させないようにします。

そして、もしタッチがウィジェットの外部でなされた場合は、Widget クラス本来のイベント super(...) を呼び出し、その結果を返すようにします。これにより、通常の場合同様、タッチイベントは (子ウィジェットに対して) 伝播します。

そして最後の11行目では:

def on_pressed(self, instance, pos):
    print ('pressed at {pos}'.format(pos=pos))

on_pressed 関数を定義しています。この関数は pressed プロパティの値が変更されたときに呼び出されます。

注釈

この on_<prop_name> イベントは、そのプロパティが定義されたクラスの中で呼び出されます。クラスの外からプロパティの値の変化を監視したい場合は、この後説明するように、そのプロパティに対してバインド (紐付け) を行う必要があります。

Binding to the property (プロパティへのバインド)

アクセスできるのがウィジェットインスタンスだけの場合、どうやってそのプロパティの変化を監視すればいいでしょうか。答えは、そのプロパティに バインド することです。

your_widget_instance.bind(property_name=function_name)

たとえば、次のコードを見てください:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 class RootWidget(BoxLayout):

     def __init__(self, **kwargs):
         super(RootWidget, self).__init__(**kwargs)
         self.add_widget(Button(text='btn 1'))
         cb = CustomBtn()
         cb.bind(pressed=self.btn_pressed)
         self.add_widget(cb)
         self.add_widget(Button(text='btn 2'))

     def btn_pressed(self, instance, pos):
         print ('pos: printed from root widget: {pos}'.format(pos=.pos))

このコードをそのまま走らせ、ボタンをクリックすると、コンソールには2つのprintの出力が現れるでしょう。1つは CustomBtn の中で呼び出された on_pressed イベントによるものであり、もう1つは pressed にバインドした、 btn_pressed 関数によるものです。

2つの関数が両方とも呼び出される理由は単純です。バインドは、オーバーライドを行うわけでは無いのです。これらの関数を両方とも作ることは冗長であり、一般には、プロパティの変化を監視し、反応する手法のうち1つを用いるべきです。

on_<property_name> イベントや、プロパティにバインドされた関数に渡される引数についても知っておくべきでしょう。

def btn_pressed(self, instance, pos):

最初の引数は self で、これには当該関数が定義されたクラスのインスタンス自身が入ります。次のようにインライン関数にもできます。

1
2
3
4
5
6
7
 cb = CustomBtn()

 def _local_func(instance, pos):
     print ('pos: printed from root widget: {pos}'.format(pos=pos))

 cb.bind(pressed=_local_func)
 self.add_widget(cb)

この場合、最初の引数 instance には当該プロパティの定義されたクラスのインスタンスが入ります。

2つ目の引数 value には、当該プロパティの新しい値が入ります。

これまでに述べたスニペットコードをまとめておきます。これをエディタにコピペすれば、試しに実行できるでしょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 from kivy.app import App
 from kivy.uix.widget import Widget
 from kivy.uix.button import Button
 from kivy.uix.boxlayout import BoxLayout
 from kivy.properties import ListProperty

 class RootWidget(BoxLayout):

     def __init__(self, **kwargs):
         super(RootWidget, self).__init__(**kwargs)
         self.add_widget(Button(text='btn 1'))
         cb = CustomBtn()
         cb.bind(pressed=self.btn_pressed)
         self.add_widget(cb)
         self.add_widget(Button(text='btn 2'))

     def btn_pressed(self, instance, pos):
         print ('pos: printed from root widget: {pos}'.format(pos=pos))

 class CustomBtn(Widget):

     pressed = ListProperty([0, 0])

     def on_touch_down(self, touch):
         if self.collide_point(*touch.pos):
             self.pressed = touch.pos
             # we consumed the touch. return False here to propagate
             # the touch further to the children.
             return True
         return super(CustomBtn, self).on_touch_down(touch)

     def on_pressed(self, instance, pos):
         print ('pressed at {pos}'.format(pos=pos))

 class TestApp(App):

     def build(self):
         return RootWidget()


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

このコードを実行すると、次のような出力が現れます:

../_images/property_events_binding.png

CustomBtn はビジュアルを何ら持たないため、真っ黒で現れます。この黒い領域をタッチもしくはクリックすれば、コンソールに print 文の出力が現れるでしょう。

Compound Properties (複合的なプロパティ)

` AliasProperty`を定義する際は、普通 getter setter を自分自身で定義します。この場合、getter/setter がいつ呼び出されるかを `bind 引数を用いて定義するのはあなた次第です。

次のコードを見てみましょう。

1
2
3
4
5
6
7
 cursor_pos = AliasProperty(_get_cursor_pos, None, bind=(
     'cursor', 'padding', 'pos', 'size', 'focus',
     'scroll_x', 'scroll_y'))
 '''Current position of the cursor, in (x, y).

 :attr:`cursor_pos` is a :class:`~kivy.properties.AliasProperty`, read-only.
 '''

ここでは cursor_posAliasProperty クラスで、その getter は _get_cursor_pos、その setter は None に設定されています。”このことは、cursor_pos がリードオンリー (書き込み不可) なプロパティであることを意味しています。

最後の bind 引数は、そこに与えられたプロパティのうちどれかが変化した場合に、on_cursor_pos イベントがディスパッチされるということを定義します。