0

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

Petr Marek
  • 595
  • 5
  • 19

2 Answers2

1

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()
amras
  • 1,499
  • 2
  • 6
  • 10
  • Ok, but with time.sleep() I wanted only demostrate some blocking code. In my real program (hundreds of code lines) when someone press button (Login) I want to rebuild layout (from another Screen) and then switch to this screen (Homepage screen). And during this rebuild (which actually takes around 3 seconds) I want to disable Login and Register Button (as feedback for user, that something is happening and he cannot do anything else until it is completed. But here my problem comes. Even If I disable buttons before calling rebuilt() function, it doesn't disable... – Petr Marek Aug 28 '20 at 19:14
  • 1
    When the button disabling is in process (the GUI update is in process), the sleep instruction executed and hence it blocks the GUI update though the sleep line of the code comes after button disabling line. In your production code, you can disable the Login button and the rebuild() function can be called through ```Clock``` e.g. ```Clock.schedule_once(lambda dt: rebuild())``` and this way it will not block disabling process. You can test this in your production environment. – amras Aug 28 '20 at 19:21
  • Oh, thanks! It really works. But I have one question if you know kivy...I simply disable Login button and Register Button. It really immediately disable both buttons (and enable after rebuild of Screen is completed), but even the code for disabling is same for both of the buttons - the first button (Login button) have dark blue tint after disabling (the second one is only more dark grey, without tint). Do you know why? I think, that maybe it is because I disable button when it is actualy "pushed"? But how I can solve it to don't have that dark blue tint? – Petr Marek Aug 28 '20 at 19:40
  • 1
    This would be solved if you trigger the function on ```on_release``` event instead of ```on_press``` event of button. – amras Aug 28 '20 at 19:48
0

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.

Max Larsson
  • 282
  • 2
  • 10
  • Can I ask, how you mean, that animation happens after the call of my press() function? Of course, that it happens after calling it, but before calling time.sleep(), so it means, that disabling of button must happen before freezing for 3 sec, right? – Petr Marek Aug 28 '20 at 18:46