Kivy: обновление текста метки будет schedule_once

#python #kivy

#python #kivy

Вопрос:

Я пытаюсь работать с событиями часов, т.Е. С schedule_once и т. Д.,

В моем тестовом коде я пытаюсь обновить текст метки с помощью schedule_once . По какой-то неизвестной причине текст метки не обновляется. но выполняется цикл for.

 from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.clock import Clock
from functools import partial
from time import sleep

kv = '''
Some:
    target: target
    
    BoxLayout:
        orientation: 'vertical'
        size: root.width, root.height
        
        Label:
            id: target
            text: 'Text will be changed'
            font_size: 32
            size_hint: 1, .5
            
        Button:
            text: 'Change'
            on_press: root.click()
'''

class Some(Widget):
    
    target = ObjectProperty(None)

    def click(self):
        
        for i in range(10):
            sleep(1)
            print(i)
            Clock.schedule_once(lambda dt: partial(self.update_text, i), 10-i)
            
    def update_text(self, k):
        self.target.text = str(k)

class TestingApp(App):
    def build(self):
        return Builder.load_string(kv)
     
if __name__ == "__main__":
    TestingApp().run()
 

Пожалуйста, помогите мне и скажите, как это реализовать.

Ответ №1:

Интересная проблема с парой проблем.

  • Самая простая проблема заключается в том, что вы вызываете Clock.schedule_once() с timeout «10 — i». Это приведет к тому, что все запланированные выполнения будут выполняться примерно в одно и то же время. Это то, что вы намеревались?
  • Ваш click() метод выполняется в основном потоке, таким образом удерживая этот поток на время for цикла в этом методе. Это предотвращает любые обновления графического интерфейса до тех пор, пока click() метод не вернется.
  • Когда вы используете a lambda в цикле и пытаетесь использовать переменную цикла (в вашем случае i ) в lambda , i переменная не вычисляется до тех lambda пор, пока не будет вычислено, что оценка выполняется после завершения цикла, поэтому i in your lambda всегда будет вычисляться 9 .
  • Когда вы используете метод в a lambda , вы должны включить () после имени метода. Странно, потому что везде вы бы просто использовали имя метода.

Итак, вы можете изменить свой Clock.schedule_once() вызов на:

 Clock.schedule_once(lambda dt, j=i: partial(self.update_text, j)(), 10 i)
 

В приведенной выше строке j=i создается еще один аргумент для lambda , который вычисляется при lambda создании. Также () добавлено в конец partial , как указано выше.

Однако этот код все еще удерживает основной поток в течение 10 секунд. Если вы просто хотите Label обновлять каждую секунду, вам не нужно использовать sleep в этом цикле, просто укажите timeout аргумент Clock.schedule_once() , который планирует изменение по желанию. Вот модифицированная версия вашего click() метода, которая объединяет все вышеперечисленное:

 def click(self):
    for i in range(10):
        Clock.schedule_once(lambda dt, j=i: partial(self.update_text, j)(), i)
 

Использование lambda и partial вместе может привести к путанице. Вот другая версия вашего кода, которая использует Clock.schedule_interval() вместо этого. Для меня этому коду легче следовать:

 from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock

kv = '''
Some:
    target: target

    BoxLayout:
        orientation: 'vertical'
        size: root.width, root.height

        Label:
            id: target
            text: root.txt
            font_size: 32
            size_hint: 1, .5

        Button:
            text: 'Change'
            on_press: root.click()
'''


class Some(Widget):
    target = ObjectProperty(None)
    txt = StringProperty('Text will be changed')


    def click(self):
        self.current_update = 0
        self.stop_update = 9
        self.clock_event = Clock.schedule_interval(self.update_text, 1)

    def update_text(self, dt):
        self.txt = str(self.current_update)
        self.current_update  = 1
        if self.current_update > self.stop_update:
            self.clock_event.cancel()


class TestingApp(App):
    def build(self):
        return Builder.load_string(kv)


if __name__ == "__main__":
    TestingApp().run()