四棠赛、編程向?qū)?4.5事件和屬性)

編程向?qū)?.5事件和屬性

事件是Kivy編程里面一個(gè)重要的部分。對(duì)于有GUI開發(fā)經(jīng)驗(yàn)的人來(lái)說也許不是那么讓人驚奇饭耳,但對(duì)于初學(xué)者是一個(gè)重要的概念串述。一旦你理解了事件如何工作讯嫂、如何綁定脯倚,你將會(huì)在Kivy到處發(fā)現(xiàn)它們做修。它們使你想利用Kivy實(shí)現(xiàn)任何的行為變得很容易贸伐。

下面的插圖顯示了在Kivy框架中事件如何被處理。

events and properties

一觅赊、介紹事件發(fā)送

Kivy框架的最重要的基類之一就是EventDispatcher類右蕊。這個(gè)類允許你注冊(cè)事件類型并發(fā)送它們到感興趣的地方(通常是其它事件發(fā)送者)。部件吮螺、動(dòng)畫饶囚、時(shí)鐘類都是事件發(fā)送的例子。

EventDispatcher對(duì)象依賴主循環(huán)生成和處理事件鸠补。

二坯约、主循環(huán)

在上面插圖中,主循環(huán)作為輪廓莫鸭。這個(gè)循環(huán)運(yùn)行在應(yīng)用程序的全部生命周期中闹丐,直到應(yīng)用程序退出時(shí)才終止。

在循環(huán)里面被因,每一次迭代卿拴,當(dāng)發(fā)生用戶輸入、傳感器或者一些其他資源梨与、畫面被渲染顯示時(shí)堕花,總會(huì)有事件生成。

你的應(yīng)用程序可以指定回調(diào)函數(shù)粥鞋,它們?cè)谥餮h(huán)中被調(diào)用缘挽。如果一個(gè)回調(diào)函數(shù)費(fèi)時(shí)太長(zhǎng)或者根本不會(huì)退出,則主循環(huán)會(huì)中斷同時(shí)你的應(yīng)用程序無(wú)法正常運(yùn)行呻粹。

在Kivy應(yīng)用程序中壕曼,你必須避免使用長(zhǎng)循環(huán)、死循環(huán)或睡眠(sleeping)等浊,如下代碼是需要避免的:

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

當(dāng)你運(yùn)行上面的代碼腮郊,則你的程序永遠(yuǎn)無(wú)法退出該循環(huán),要預(yù)防Kivy做類似的事情筹燕。結(jié)果將會(huì)看到一個(gè)無(wú)法交互的黑色的窗口轧飞。正確的方式的,你需要定制(schedule)你的animate_somthing()函數(shù)重復(fù)調(diào)用撒踪。

(一)重復(fù)事件

你可以使用schedule_interval()每隔X時(shí)間調(diào)用一個(gè)函數(shù)或方法过咬,下面是一個(gè)例子,每隔1/30秒調(diào)用一次my_callback函數(shù):

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

你有兩種方法來(lái)取消前面定制的事件制妄,第一種是:

Clock.unschedule(my_callback)

或者你在你的回調(diào)函數(shù)中返回False掸绞,那么你的事件將會(huì)自動(dòng)取消:

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.)
(二)單次事件

使用schedule_once(),你可以定制執(zhí)行一次你的回調(diào)函數(shù)忍捡,比如在下一幀集漾,或X秒后:

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

上面的代碼中,my_callback()函數(shù)將會(huì)在1秒后執(zhí)行砸脊。1秒?yún)?shù)是在執(zhí)行該程序前等待的時(shí)間具篇,以秒為單位。但是你可以使用特殊的值作為時(shí)間參數(shù)得到一切其它結(jié)果:

  • 如果X > 0凌埂,則回調(diào)函數(shù)會(huì)在X秒后執(zhí)行驱显。
  • 如果X = 0, 則回調(diào)函數(shù)會(huì)在下一幀執(zhí)行。
  • 如果x = -1瞳抓,則回調(diào)函數(shù)在在下一幀之前執(zhí)行埃疫。

其中 x = -1最為常用。

重復(fù)執(zhí)行一個(gè)函數(shù)的第二種方法是:一個(gè)回調(diào)函數(shù)使用schedule_once遞歸調(diào)用了自己孩哑,在外部schedule_once函數(shù)中又調(diào)用了該回調(diào)函數(shù):

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

當(dāng)主循環(huán)嘗試保持定制請(qǐng)求時(shí)栓霜,當(dāng)恰好一個(gè)定制的回調(diào)函數(shù)被調(diào)用時(shí),有一些不確定的情況會(huì)發(fā)生横蜒。有時(shí)另外一些回調(diào)函數(shù)或一些任務(wù)花費(fèi)了超出預(yù)期的時(shí)間胳蛮,則定時(shí)會(huì)被延遲。

在第二種解決方案中丛晌,在上一次迭代執(zhí)行結(jié)束后仅炊,下一次迭代每秒至少會(huì)被調(diào)用一次。而使用schedule_interval()澎蛛,回調(diào)函數(shù)則每秒都會(huì)被調(diào)用抚垄。

(三)事件跟蹤

如果你想為下一幀定制一個(gè)僅執(zhí)行一次的函數(shù),類似一個(gè)出發(fā)器谋逻,你可能這樣做:

Clock.unschedule(my_callback)
Clock.schedule_once(my_callback, 0)

這種方式的代價(jià)是昂貴的呆馁,因?yàn)槟憧偸钦{(diào)用unschedule()方法,無(wú)論你是否曾經(jīng)定制過它毁兆。另外智哀,unschedule()方法需要迭代時(shí)鐘的弱引用列表,目的是找到你的回調(diào)函數(shù)并移除它荧恍。替代的方法是使用出發(fā)器:

trigger = Clock.create_trigger(my_callback)
#隨后
trigger()

每次你調(diào)用trigger瓷叫,它會(huì)為你的回調(diào)函數(shù)定制一個(gè)信號(hào)調(diào)用,如果已經(jīng)被定制送巡,則不會(huì)重新定制摹菠。

三、部件事件

每個(gè)部件都有兩個(gè)默認(rèn)的事件類型:

  • 屬性事件(Property event):如果你的部件改變了位置或尺寸骗爆,則事件被觸發(fā)次氨。
  • 部件定義事件(Widget-defined event):當(dāng)一個(gè)按鈕被按下或釋放時(shí),事件被觸發(fā)摘投。

四煮寡、自定義事件

為了創(chuàng)建一個(gè)自定義事件虹蓄,你需要在一個(gè)類中注冊(cè)事件名,并創(chuàng)建一個(gè)同名的方法:

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

    def do_something(self, value):
        #當(dāng)do_something被調(diào)用時(shí)幸撕,on_test事件將會(huì)連同value被發(fā)送
        self.dispatch('on_test', value)

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

五薇组、附加回調(diào)

為了使用事件,你必須綁定回調(diào)函數(shù)坐儿。當(dāng)事件被發(fā)送時(shí)律胀,你的回調(diào)函數(shù)將會(huì)連同參數(shù)被調(diào)用。

一個(gè)回調(diào)函數(shù)可以是任何python函數(shù)貌矿,但是你必須確保它接受事件發(fā)出的參數(shù)炭菌。因此,使用*args的參數(shù)會(huì)更安全逛漫,這樣將會(huì)在args列表中接收到所有的參數(shù)黑低。例如:

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

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

有關(guān)附加回調(diào)函數(shù)更多的示例可以參閱kivy.event.EventDispatcher.bind()文檔

六、屬性介紹

屬性是一個(gè)很好的方法用來(lái)定義事件并綁定它們酌毡。本質(zhì)上來(lái)說投储,當(dāng)你的對(duì)象的特征值發(fā)生變化時(shí),它們創(chuàng)造事件阔馋,所有的引用特征值的屬性都會(huì)自動(dòng)更新玛荞。

有不同類型的屬性來(lái)描述你想要處理的數(shù)據(jù)類型。

  • StringProperty
  • NumericProperty
  • BoundedNumericProperty
  • ObjectProperty
  • DictProperty
  • ListProperty
  • OptionProperty
  • AliasProperty
  • BooleanProperty
  • ReferenceListProperty

七呕寝、聲明屬性

為了聲明屬性勋眯,你必須在類的級(jí)別進(jìn)行。當(dāng)你的對(duì)象被創(chuàng)建時(shí)下梢,該類將會(huì)進(jìn)行實(shí)際特征值的初始化客蹋。特寫屬性不是特征值,它們是基于你的特征值創(chuàng)建事件的機(jī)制孽江。

class MyWidget(Widget):
    text = StringProperty('')

當(dāng)重載init時(shí)讶坯,總是接受**kwargs參數(shù)并使用super()調(diào)用父類的init方法:

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

八、發(fā)送屬性事件

Kivy的屬性岗屏,默認(rèn)提供一個(gè)on_<property_name>事件辆琅。當(dāng)屬性值改變時(shí)該事件被調(diào)用。

注意这刷,如果新的屬性值等于當(dāng)前值婉烟,該事件不會(huì)被調(diào)用。
例如:

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])

我們定義了pressed屬性暇屋,類型為L(zhǎng)istProperty,默認(rèn)值為[0, 0],當(dāng)屬性值發(fā)生改變時(shí)似袁,on_pressed事件被調(diào)用。

在第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)

我們重載了on_touch_down()方法,我們?yōu)槲覀兊牟考隽伺鲎矙z測(cè)昙衅。

如果觸摸發(fā)生在我們的部件內(nèi)部扬霜,我們改變touch.pos按下的值并返回True,表明我們處理了這次觸摸并不想它繼續(xù)傳遞。

最后而涉,如果觸摸發(fā)生在我們的部件外部著瓶,我們使用super()調(diào)用原始事件并返回結(jié)果。它允許觸摸事件繼續(xù)傳遞婴谱。

最后在11行:

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

我們定義了on_pressed函數(shù),當(dāng)屬性值改變時(shí)躯泰,該函數(shù)被調(diào)用谭羔。

注意當(dāng)屬性值被定義時(shí),on_<prop_name>事件在類內(nèi)部被調(diào)用麦向。為了在類的外部監(jiān)控或觀察任何屬性值的變動(dòng)瘟裸,你可以以下面的方式綁定屬性值。

your_widget_instance.bind(property_name=function_name)

例如诵竭,考慮以下代碼:

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))

如果你運(yùn)行上面的代碼话告,你會(huì)注意到在控制臺(tái)有兩個(gè)打印信息。一個(gè)來(lái)自on_pressed事件卵慰,該事件在CustomBtn類內(nèi)部被調(diào)用沙郭,另一個(gè)來(lái)自我們綁定屬性改變的btn_pressed函數(shù)

你也需要注意到傳遞給on_<property_name>事件的參數(shù)及綁定屬性的函數(shù)。

def btn_pressed(self, instance, pos):

第一個(gè)參數(shù)是self裳朋,是該函數(shù)被定義的類的實(shí)例病线。你可以如下面的方式使用一個(gè)內(nèi)聯(lián)函數(shù):

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)

第一個(gè)參數(shù)是屬性被定義的類的實(shí)例。
第二個(gè)參數(shù)是屬性的新的值鲤嫡。
下面是一個(gè)完整的麗日送挑,你能拷貝下來(lái)進(jìn)行實(shí)驗(yàn)。

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()

運(yùn)行結(jié)果如下:

property_events_binding

我們的定制按鈕沒有可視的表述暖眼,因此顯示一個(gè)黑塊惕耕。你能觸摸或點(diǎn)擊它以在控制臺(tái)查看輸出。

九诫肠、混合屬性

當(dāng)定義一個(gè)AliasProperty時(shí),通常的做法是定義getter()和setter函數(shù)司澎。當(dāng)getter()和setter()函數(shù)使用bind被調(diào)用時(shí),它落在了你的肩上栋豫〔宴郑考慮以下代碼:

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_pos是一個(gè)AliasProperty,它使用_get_cursor_pos作為getter(),并且setter()為None笼才,表明這是一個(gè)只讀屬性漱受。

在最后,當(dāng)任何使用bind=argument的屬性改變時(shí),on_cursor_pos事件被發(fā)送昂羡。

下節(jié)預(yù)告:編程向?qū)?.6輸入管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末絮记,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子虐先,更是在濱河造成了極大的恐慌怨愤,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛹批,死亡現(xiàn)場(chǎng)離奇詭異撰洗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)腐芍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門差导,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人猪勇,你說我怎么就攤上這事设褐。” “怎么了泣刹?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵助析,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我椅您,道長(zhǎng)外冀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任掀泳,我火速辦了婚禮锥惋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘开伏。我一直安慰自己膀跌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布固灵。 她就那樣靜靜地躺著捅伤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巫玻。 梳的紋絲不亂的頭發(fā)上丛忆,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音仍秤,去河邊找鬼熄诡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诗力,可吹牛的內(nèi)容都是我干的凰浮。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼袜茧!你這毒婦竟也來(lái)了菜拓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤笛厦,失蹤者是張志新(化名)和其女友劉穎纳鼎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裳凸,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贱鄙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姨谷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逗宁。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖菠秒,靈堂內(nèi)的尸體忽然破棺而出疙剑,到底是詐尸還是另有隱情氯迂,我是刑警寧澤践叠,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站嚼蚀,受9級(jí)特大地震影響禁灼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轿曙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一弄捕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧导帝,春花似錦守谓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至虐秦,卻和暖如春平酿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悦陋。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工蜈彼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俺驶。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓幸逆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秉颗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容