Cannot disable buttons in Kivy (Python) - python

I have problem with disabling of buttons in kivy library. When I disable button, it simply not disable. It waits in some strange way.
Let me show you my code:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
import time
class MainApp(App):
def build(self):
self.l = FloatLayout()
b = Button(text="BUTTON", pos_hint={"top":0.8, "right": 0.8}, size_hint=(0.6, 0.6))
b.bind(on_press=self.press)
self.l.add_widget(b)
return self.l
def press(self, btn):
btn.disabled = True
time.sleep(3.0)
btn.disabled = False
app = MainApp()
app.run()
When I press button, I want to disable it for 3 sec. But instead of it program "freeze" (without disabling of button), and then after 3 secs do animation of press (button blinks with blue color). Of cource program must "freeze" because of time.sleep(3.0), but after disabling of button (Which must be gray, but it dont change color...)
How to solve it? If I put there instead time.sleep() something like for cycle (with about 10 milions of cycle) to imitate of "doing something" by program, it behaves in the same way...
So how I can solve it? How to disable button in kivy, then do something and after it is done enable button again?
Thanks!
EDIT: My problem isn't, that program freezes for 3 seconds. I understand that calling time.sleep() is blocking. What I don't understand is why button is not disabled before (and during) sleep...

The time.sleep is blocking the code. Instead you need to use Clock to enable the button after 3 seconds. Below is the corrected code to achieve your target:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from functools import partial
class MainApp(App):
def build(self):
self.l = FloatLayout()
b = Button(text="BUTTON", pos_hint={"top":0.8, "right": 0.8}, size_hint=(0.6, 0.6))
b.bind(on_press=self.press)
self.l.add_widget(b)
return self.l
def press(self, btn):
btn.disabled = True
Clock.schedule_once(partial(self.btn_enable, btn), 3)
def btn_enable(self, btn, *args):
btn.disabled = False
app = MainApp()
app.run()

TL; DR
The animation happens after the press function is called. This means that you freeze the program when doing time.sleep.
What to do about it?
Instead, you need to do something non-blocking, meaning that it runs in three seconds, but it doesn't cause the program to freeze. Something that would probably work is to utilize threads (something similar to the example, but dealing with sending variables across threads).
Example
Here is an example for your code that does not work, so you can understand the gist of it. Most likely, you are going have to deal with passing variables across threads:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
# import time
import threading
class MainApp(App):
def build(self):
self.l = FloatLayout()
b = Button(text="BUTTON", pos_hint={"top":0.8, "right": 0.8}, size_hint=(0.6, 0.6))
b.bind(on_press=self.press)
self.l.add_widget(b)
return self.l
def press(self, btn):
btn.disabled = True
# time.sleep(3.0)
threading.Timer(3.0, lambda: btn.disabled = False).start()
app = MainApp()
app.run()
This was inspired by this answer.

Related

How do I create a button, and assign a function to it? Python, Kivy

I am a beginner in Python and Kivy. I want to create a simple program that should have an exit button on a window that must exit the app when pressed. Hi there, please help me. It's a request, please keep the code simple for a beginner. ^_^
Okay, I don't know how your code is, but this code could be copy-pasted as is and it will work (just import the necessary modules from kivy though):
.py
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
class yourscreen(FloatLayout):
def __init__(self, **kwargs):
#NEEDED
super(yourscreen, self).__init__(**kwargs)
self.button = Button(
text='exit',
size_hint=(0.5, 0.2)
)
self.button.bind(on_release= lambda x:self.exit())
self.add_widget(self.button)
def exit(self):
return MyApp().stop()
class MyApp(App):
def build(self):
return yourscreen()
if __name__ == '__main__':
MyApp().run()
You could pull out what you need which is MyApp().stop()

Image not showing when created from a thread kivy/kivymd

I'm working on an app, and I need the images to display independently at a specific timing. I have set up a thread using python's stock threading module, it runs and works normally instead of the image it displays a black square. Does anyone know how to fix it?
Here is my code to reproduce the issue:
import threading
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
class TestApp(App):
def build(self):
self.fl = FloatLayout()
self.fl.add_widget(Button(text="show image", on_press=self.start_thread))
return self.fl
def insertfunc(self):
self.fl.add_widget(Image(source="HeartIcon.png"))
def start_thread(self, instance):
threading.Thread(target=self.insertfunc).start()
TestApp().run()
Any help will be appreciated!
The add_widget() must be done on the main thread. I assume that you are using threading because you have additional things to do on the Thread aside from just the add_widget(). Based on that assumption, here is a modified version of your code that does what I think you want:
import threading
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
class TestApp(App):
def build(self):
self.fl = FloatLayout()
self.fl.add_widget(Button(text="show image", on_press=self.start_thread))
return self.fl
def insert_image(self, dt):
self.fl.add_widget(Image(source="HeartIcon.png"))
def insertfunc(self):
# do some calculations here
Clock.schedule_once(self.insert_image)
def start_thread(self, instance):
threading.Thread(target=self.insertfunc).start()
TestApp().run()
If you are not doing anything else in the new thread, then you don't actually need another thread. The start_thread() method can just do the:
self.fl.add_widget(Image(source="HeartIcon.png"))

Kivy TabbedPanel switch_to work inconsistently

I am writing a code which starts the frontend, runs the backend and then loads the frontend. The frontend consists of TabbedPanel, and the currently displayed tab may be change by backend.
Here's the MRE:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
def button(instance):
instance.parent.parent.switch_to(instance.parent.parent.tab_2) # accessing TabbedPanel without messing with sending
# a variable
def backend(frontend):
# this class represents main backend function. In the result of its execution there might be a need to switch to
# another tab
frontend.switch_to(frontend.tab_2)
class MyTabbedPanel(TabbedPanel):
def __init__(self, **kwargs):
super().__init__()
self.tab_1 = TabbedPanelHeader()
self.tab_2 = TabbedPanelHeader()
self.tab_1.content = Button(text='Tab 1')
self.tab_1.content.bind(on_release=button)
self.tab_2.content = Label(text='Tab 2')
self.add_widget(self.tab_1)
self.add_widget(self.tab_2)
class Application(App):
def build(self):
frontend = MyTabbedPanel()
backend(frontend)
return frontend
Application().run()
The button, which I have added to compare, to switch from tab 1 to tab 2 works just fine, however, the auto swith when starting the app does not work.
What is the problem? Thank you in advance.
At the time that you're calling backend, there is no root widget returned by the build method, let alone a tab to switch to.
One way to solve this, is to schedule the call to the backend for after the build ends, using the Clock module.
def build(self):
frontend = MyTabbedPanel()
# backend(frontend)
from functools import partial
from kivy.clock import Clock
Clock.schedule_once(partial(backend, frontend))
return frontend
You also have to add an args argument to the backend method, because Clock sends a dt value:
def backend(frontend, *args):

Running functions in kivy extremely frequently (Kivy Clock/FreeClock)

I am working on an app that takes in data from a Bluetooth device (that I handle simply through the serial module). That I know how to do. But giving it a new life in Kivy - displaying it - creates a problem of everything being synced with FPS. I want to run the function all the time in the background, hundreds of times a second. It's fair to say that out of 10000 incoming packets 10 might be useful. So if I would go by clock scheduling it would have to be (well) under 20 ms per cycle.
Simply put: how do I run one of the functions separately from the FPS? Is there a clean way of using the Free version of the clock for only one function and how is that achieved?
I want a simple solution, I can reinvent the wheel, but I don't want to.
Any help is appreciated, thank you.
You could use threading for this.
Here is a little example of using threading with kivy:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.properties import NumericProperty
import threading
import time
Builder.load_string('''
<MyLayout>:
Label:
text: str(root.data)
''')
class MyLayout(BoxLayout):
data = NumericProperty(0)
count = 0
running = True
def __init__(self,**kwargs):
super(MyLayout,self).__init__(**kwargs)
Clock.schedule_once(self.after_init)
def after_init(self, dt):
threading.Thread(target=self.func).start()
def func(self):
while self.running:
self.data += 1
time.sleep(0.1)
class MyApp(App):
def build(self):
self.root = MyLayout()
return self.root
def on_stop(self):
self.root.running = False
MyApp().run()

Kivy: how to make each buttons play different sound

So I'm writing a drum pad:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.core.audio import SoundLoader
class GridAction(Button):
pass
class MakingGrid(GridLayout):
def __init__(self, *args, **kwargs):
super(MakingGrid, self).__init__(*args, **kwargs)
for i in range(16):
grid_action = GridAction()
grid_action.bind(on_release=self.button_pressed)
self.add_widget(grid_action)
def button_pressed(self, button):
print('pressed')
class MyApp(App):
def build(self):
return MakingGrid(cols=4)
if __name__ == "__main__":
MyApp().run()
There is a grid of buttons as a result, and the purpose is to make them sound different, but I don't know how to do this. Please, help. Thanks in advance) Also, if you see any disadvantages in this code, please, tell me about them, I`m a beginner.
A simple way to play audio is with kivy's soundloader.
Beyond this, I'm not clear on what your question actually is. The general answer is to do something like give each GridAction its own audio_file property holding a filepath to the sound you want, and bind the on_press events to play the sound at this location.

Categories