Reputation: 23
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
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