Situation: Hello, I have a kivy application with buttons/icons which have bindings like changing page, saving/loading data with AWS or local storage, calling an API REST, etc...
Problem: Some of theses actions take some time and when I click multiple time on an icon that take time to do an action, my application crash on android.
Solution: Every time a binding is called, I disable the possibility of the user to interact with the application and I display a little "charging icon" on the menu.
Real problem: I don't know how to do it ! Is there a boolean userCanInteract or 2 functions like enable_user_interaction() disable_user_interaction() ?
I don't know a way to disable all user interaction with your app but you can disable your button like this:
Python file:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
class Window(Widget):
button = ObjectProperty(None)
def disable_button(self):
self.button.disabled = True
self.button.text = "Disabled"
class GUI(App):
def build(self):
return Window()
if __name__ == "__main__":
GUI().run()
.kv file:
<Window>
button: button
Button:
id: button
text: "Enabled"
disabled: False
on_release: root.disable_button()
For the crashing part, i think using threading module can solve your problem. Every time you click a button for long tasks, create a new thread. Example:
Python:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
import threading
from time import sleep
def do_something(app):
print("This function is doing something.")
sleep(1)
app.root.button.disabled = False
app.root.button.text = "Enabled"
class Window(Widget):
button = ObjectProperty(None)
def disable_button(self, app):
thread = threading.Thread(target=do_something, args=(app,))
self.button.disabled = True
self.button.text = "Disabled"
thread.start()
class GUI(App):
def build(self):
return Window()
if __name__ == "__main__":
GUI().run()
.kv file:
<Window>
button: button
Button:
id: button
text: "Enabled"
disabled: False
on_release: root.disable_button(app)
This way, your button will be disabled until the thread is done. When the thread ends, button will be enabled.
Related
I'm trying to create a small GUI with kivy. by clicking on the button named button it should launch the popup on another thread with a progress bar that evolves after 3 seconds. But kivy gives me an error saying "Cannot create graphics instruction outside the main Kivy thread"
how to solve this problem?
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.lang import Builder
import time
import threading
Builder.load_string("""
<Interface>:
orientation: 'vertical'
Label:
text: "Test"
BoxLayout:
orientation: 'vertical'
Label
text: "phone Number"
TextInput:
id: variable
hint_text: ""
Thebutton:
user_input: variable.text
text: "Buttion"
on_release: self.do_action()
""")
class Interface(BoxLayout):
pass
class Thebutton(Button):
def bar_de_progress(self):
bdp = ProgressBar()
poo = Popup(title="Brute Forcing ...", content=bdp, size_hint=(0.5, 0.2))
poo.open()
time.sleep(1)
bdp.value = 25
def do_action(self, *args):
threading.Thread(target=self.bar_de_progress).start()
return
class MyApp(App, Thebutton):
def build(self):
return Interface()
if __name__ == "__main__":
MyApp().run()
Im a bit late but i just solve the same problem by changing the 2.1.0 kivy version to the 2.0.0 version.
I cannot replicate your error, but the general rule is don't do GUI operations on threads other than the main thread. Here is a modified version of your Thebutton class that does that:
class Thebutton(Button):
def bar_de_progress(self):
# this is run on the main thread
self.bdp = ProgressBar()
poo = Popup(title="Brute Forcing ...", content=self.bdp, size_hint=(0.5, 0.2))
poo.open()
threading.Thread(target=self.update_progress).start()
def update_progress(self):
# this is run on another thread
time.sleep(1)
self.bdp.value = 25
def do_action(self, *args):
self.bar_de_progress()
It seems to me that the color change when clicking a button with a mouse only occurs if I hold the button pressed for a short time (< 1 second). If I only perform a very short click, it doesn't happen. Did anyone else experience this and is there a way to change this behaviour, so the button color changes immediately (preferably directly in the kv file)?
I am using Kivy 2.0.0.
Edit:
Meanwhile I noticed that this happens when the operations triggered by the button press take some time. Here is an example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
import time
import kivy
kivy.require('2.0.0')
Builder.load_string("""
<MyLayout>
Button:
text: "Button 1"
on_press: root.button1_press()
Button:
text: "Button 2"
on_release: root.button2_press()
""")
class MyLayout(BoxLayout):
def button1_press(self):
time.sleep(3)
print("Result Button 1")
def button2_press(self):
time.sleep(3)
print("Result Button 2")
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == '__main__':
MyApp().run()
To change the color of button 1 I need to press it for at least 3 seconds (the sleep time), while button 2 stays on for 3 seconds cause on_release it used instead of on_press for binding.
Is there a way to just have a button change color immediately like a short "blink" to give the feedback that it has been pressed?
I would like to run a Method when the user tries to exit the app , kind of like a "are you sure you want to exit" or "Do you want to save the file" type of message whenever the user tries to exit by clicking the Exit button on top of the window
Some thing like
on_quit: app.root.saveSession()
If you want your application to simply run things after the GUI has closed, the easiest and smallest approach would be to place any exit code after TestApp().run(). run() creates a endless loop which also clears any event-data from within kivy so it doesn't hang. That endless loop breaks as soon as the window/gui instance dies. So there for, any code after will execute only after the GUI dies too.
If you want to create a graceful shutdown of the GUI with for instance socket-closing events or a popup asking the user if that's what they really want to do, then creating a hook for the on_request_close event is the way to go:
from kivy.config import Config
Config.set('kivy', 'exit_on_escape', '0')
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.core.window import Window
class ChildApp(App):
def build(self):
Window.bind(on_request_close=self.on_request_close)
return Label(text='Child')
def on_request_close(self, *args):
self.textpopup(title='Exit', text='Are you sure?')
return True
def textpopup(self, title='', text=''):
"""Open the pop-up with the name.
:param title: title of the pop-up to open
:type title: str
:param text: main text of the pop-up to open
:type text: str
:rtype: None
"""
box = BoxLayout(orientation='vertical')
box.add_widget(Label(text=text))
mybutton = Button(text='OK', size_hint=(1, 0.25))
box.add_widget(mybutton)
popup = Popup(title=title, content=box, size_hint=(None, None), size=(600, 300))
mybutton.bind(on_release=self.stop)
popup.open()
if __name__ == '__main__':
ChildApp().run()
Courtesy of pythonic64 who created a gist on the topic in a issue way back when.
I've defined two buttons: one in kv and one in Python. They are located in different screens and are used to navigate between them. What I found strange is that the button that was defined in Python successfully switched the screen, while the one defined in kv did not. Perhaps I'm not accessing the App class method properly?
Here is the code of the issue:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
Builder.load_string('''
<MyScreen1>:
Button:
id: my_bt
text: "back"
on_release: app.back
''')
class MyScreen1(Screen):
pass
class TestApp(App):
def here(self, btn):
self.sm.current = "back"
def back(self, btn):
self.sm.current = "here"
def build(self):
self.sm = ScreenManager()
s1 = Screen(name = "here")
bt = Button(text = "here",
on_release = self.here)
s2 = MyScreen1(name = "back")
#s2.ids['my_bt'].bind(on_release = self.back)
self.sm.add_widget(s1)
s1.add_widget(bt)
self.sm.add_widget(s2)
return self.sm
TestApp().run()
So if I define the switching function in kv (on_release), I can't go to the "here" screen. But if I uncomment that line in Python and comment the on_release: app.back instead, everything works fine.
I'm pretty sure that this is the correct way to access the current app, since it doesn't give me any errors (which means that the method was successfully located)
That's a subtle difference between kv and python: In kv you actually have to write the callback as a function call (a python expression), in this case:
on_release: app.back(self)
I want to make a simple program that is just showing definitions that are stored in text file.One label and button to show next definition. I try to do it with documentation but i cannot find how to load text into label. Can someone show me to some good resources or code samples ?
My code for now (i want to build in on top of example from kivy website):
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
return Label(text = 'Hello world')
if __name__ == '__main__':
MyApp().run()
The easiest way to update widgets in the UI are by binding to their properties. This can be done in code, but the real power of kivy in my opinion comes from using it's declarative UI language. Using kv, you get automatic binding.
Here is a quick example of what you might do:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
kv = '''
BoxLayout:
orientation: 'vertical'
Label:
text: app.text
Button:
text: 'click me'
on_press: app.clicked()
'''
class MyApp(App):
text = StringProperty("hello world")
def build(self):
return Builder.load_string(kv)
def clicked(self):
self.text = "clicked!"
if __name__ == '__main__':
MyApp().run()
In the kv description of the UI, you tell kivy that you want the text on the Label to be bound to a StringProperty on the app which you defined on the class. The auto-binding means that anytime you set a value to that property (like in the clicked function), the UI will update with the new value automatically.