z_nelson
z_nelson

Reputation: 23

Kivy - How do I call trigger_action() in a loop and have it function properly?

I'd like to simulate a button pressed repeatedly. Example: If I press Button 2, Button 1 should activate and light up the number of times set in the for loop on a set time interval. I've simplified the code below.

The problem is that the 'duration' attribute in the trigger_action() method does not properly regulate the timing of the loop and the print function and buttons lighting up occurs instantaneously.

I have tried to insert a time.sleep() function within the for loop to "slow it down". This does work to properly time the print function, however, the simulated button presses (button lighting up), does not occur on a regular interval but rather all at once.

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button


class TestApp(App):
    def build(self):
        layout = BoxLayout()
        self.b1 = Button(text="Button 1", on_press=self.on_press_button_1)
        self.b2 = Button(text="Button 1", on_press=self.on_press_button_2)
        layout.add_widget(self.b1)
        layout.add_widget(self.b2)
        return layout

    def on_press_button_1(self, *args):
        print("on press button 1")

    def on_press_button_2(self, *args):
        for x in range(5):
            self.b1.trigger_action(0.5)
            print(x, "on press button 2")

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

Upvotes: 2

Views: 1467

Answers (1)

John Anderson
John Anderson

Reputation: 38962

When programming using kivy, you must keep in mind that all the GUI events take place on the main thread, and all your code (unless you take precautions) will also be running on the main thread. So your for loop is running on the main thread, and if you add a sleep to that loop, it is also running on the main thread. So that code is keeping the main thread busy. The result is that all your trigger_action calls (and any other calls that change the GUI) must wait until your code stops holding the main thread before they can affect the GUI. So, at the end of your for loop, the GUI gets a chance to catch up, and all the GUI button effects that have been waiting are preformed and appear as one long button press. In general, you want any method that is called by a GUI event (like a button press), to return as quickly as possible or create a new thread to do lengthy processing.

So here is a way to make your code work:

from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button


class TestApp(App):
    def build(self):
        layout = BoxLayout()
        self.b1 = Button(text="Button 1", on_press=self.on_press_button_1)
        self.b1.click_count = 0
        self.max_clicks = None
        self.clock_event = None
        self.b2 = Button(text="Button 2", on_press=self.on_press_button_2)
        layout.add_widget(self.b1)
        layout.add_widget(self.b2)
        return layout

    def on_press_button_1(self, *args):
        print("on press button 1")
        if self.max_clicks is not None and self.clock_event is not None:
            self.b1.click_count += 1
            if self.b1.click_count >= self.max_clicks:
                self.clock_event.cancel()
                self.clock_event = None
                self.max_clicks = None

    def on_press_button_2(self, *args):
        self.max_clicks = 5
        self.b1.click_count = 0
        self.clock_event = Clock.schedule_interval(lambda dt: self.b1.trigger_action(), 1)

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

This uses Clock.schedule_interval which schedules something to run on the main thread, but returns immediately, allowing the GUI to operate normally. The Clock.schedule_interval() calls the specified method with an argument of dt (the time since the event was scheduled), but trigger_action() doesn't want such an argument, so I used a lambda. See documentation. The click_count, max_clicks, and clock_event are just used to manage cancelling the event.

Upvotes: 2

Related Questions