Cannot create graphics instruction outside the main Kivy thread - python

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()

Related

Kivy: How to blick all user interaction?

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.

How to make kivy app from different py files

I am trying to make an app out of different .py files. But I don't know how to add them together, I have one main file, and one login file with plans to add a lot more, but with these I'm experimenting right now. They are pretty basic for now until I figure out this "bonding" between them and then I will start adding some more complex stuff. I tried couple of things and they didn't work, but I left them in code for you to see (I tried to make the app to start with MainWindow, and on press of the first button it goes to login page*). Here's the code and please help me.
*Right now when I press the button it gives me this error: OSError: exception: access violation writing 0x0000000080006010
this is main.py:
from kivy.lang import Builder
from kivy.app import App
import login
from kivy.uix.screenmanager import Screen
kv = Builder.load_string('''
<MainWindow>:
GridLayout:
cols:1
GridLayout:
rows:5
Button:
text:"NOVA ROBA"
on_release:
root.call_login()
Button:
text:"KUPCI"
Button:
text:"PRODATO"
Button:
text: "AGRONOMI"
Button:
text: "STANJE U MAGACINU"
''')
class MainWindow(Screen):
def call_login(self):
login.app().run()
pass
class main_app(App):
def build(self):
return MainWindow()
if __name__ == '__main__':
main_app().run()
this is login.py:
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.app import App
class Login(Screen, App):
def build(self):
return
pass
kv = Builder.load_string('''
<Login>:
name:"login"
GridLayout:
rows:2
GridLayout:
cols:2
Label:
text:"Password: "
TextInput:
id:passwd
multiline: False
Button:
text: "Submit"
on_release:
passwd.text = ""
''')
class app(App):
def build(self):
return Login()
if __name__ == "__main__":
app().run()
You are creating 2 apps, which is not needed. Instead of inheriting from both Screen and App in the Loginscreen, inherit only from Screen. Then create a ScreenManager in your main.py's build method and then add the imported loginscreen as a widget, to switch to the new screen, use self.manager.current = "login" in the call_login method of MainWindow
class app(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MainWindow())
sm.add_widget(Login())
return sm

Button Text does not Update

>> BACKGROUND :
I want to update/change the text of a Button in the SecondScreen with a press of a Button in the MainScreen. Well I did some research and did what I want, and when I checked in the terminal the text did change. BUUT, the text shown on the SecondScreen did not.
>> THIS IS WHAT I DID :
((Mind you that I'm only using snippets of code for example, I'm going to post the whole code below.))
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.funcself()
## on press it goes to it's root and do the "funcself" function in it
which is :
class MainScreen(Screen):
def funcself(self):
app.second.funcscreen()
## it re-directs to the SecondScreen and do the "funcscreen" function
which is :
class SecondScreen(Screen):
def funcscreen(self):
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
and then I checked if I did it successfully by doing print(self.ids["button"].text), and yes!
It did change, but when I navigated to the next screen, the text shown still didn't change.
Anyone mind helping and explaining?
FULL CODE :
python file :
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class MainScreen(Screen):
def funcself(self):
app.second.funcscreen()
class SecondScreen(Screen):
def funcscreen(self):
value = self.ids["button"]
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
kv = Builder.load_file("reproduce.kv")
class reproduce(App):
second = SecondScreen()
def build(self):
return kv
def change_screen(self, x):
scrnmngr = self.root.ids["sm"]
scrnmngr.current = x
def check(self):
print(self.second.ids["button"].text)
if __name__ == "__main__":
app = reproduce()
app.run()
kivy file :
<MainScreen>:
GridLayout:
rows:2
Label:
text: "PRESS TO GO TO THE NEXT PAGE"
GridLayout:
cols:2
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.funcself()
Button:
text:">>>"
on_press:
app.change_screen("second")
root.manager.transition.direction = "left"
<SecondScreen>:
GridLayout:
rows:2
Label:
id:label
text: "PRESS TO CHECK AND RETURN TO PREV PAGE"
Button:
id:button
text:"TEXT BEFORE CHANGE"
on_press:
app.change_screen("first")
root.manager.transition.direction = "right"
app.check()
GridLayout:
cols: 1
ScreenManager:
id:sm
MainScreen:
id:main
name:"first"
SecondScreen:
id:second
name:"second"
Root Cause
It did not change because there are two instances of SecondScreen() i.e. one instantiated in the kv file and the other one instantiated in the App class, reproduce(). The view presented is created from the kv file and the second instance does not has a view associated to it.
Solution
There are two solutions to the problem, and remove second = SecondScreen() from the App class.
Kivy Screen ยป default property manager
Each screen has by default a property manager that gives you the
instance of the ScreenManager used.
Using get_screen()
class MainScreen(Screen):
def funcself(self):
self.manager.get_screen('second').funcscreen()
Using App.get_running_app() & ids
class MainScreen(Screen):
def funcself(self):
App.get_running_app().root.ids.second.funcscreen()
Example
In the following example, there are two solutions provided but one of it is commented off.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
class MainScreen(Screen):
def funcself(self):
self.manager.get_screen('second').funcscreen()
# App.get_running_app().root.ids.second.funcscreen()
class SecondScreen(Screen):
def funcscreen(self):
value = self.ids["button"]
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
kv = Builder.load_file("reproduce.kv")
class reproduce(App):
def build(self):
return kv
def change_screen(self, x):
scrnmngr = self.root.ids["sm"]
scrnmngr.current = x
def check(self):
print(self.second.ids["button"].text)
if __name__ == "__main__":
reproduce().run()
Output
The second attribute you define in your app class, is a new instantiation of the screen, and not really the instance you got in your screenmanager, which you add in kv. This is why when you check, you see its changed, but not on the right instance. And again when you call app.second.func, from mainscreen, again its the wrong instance.
But your app always has a root. In your case its the gridlayout. And every screen has a manager. There are a couple of ways to acces it. But you can do like this.
In your mainscreen class in kv:
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.manager.get_screen("second").ids["button"].text = "Something"
Here it gets the screenmanager, and uses its get_screen() method to get the screen named second, and then the id's of that kv rule.

Kivy reStructuredText renderer popup?

i want popup effect for references in kivy reStructuredText renderer, in default when we press any reference it scrolls to the reference, here i want to replace scroll with a popup, i have been searching for this for a long time but in vain, today i found goto(ref, *largs)in kivy docs, is it possible to call a reference popup using this? is it possible to redefine RstDocument fuctions in another class and use it?? i am a beginner.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<RSTGUI>:
RstDocument:
text: root.doc
goto: # Any Popup aur bubble with reference
""")
class RSTGUI(BoxLayout):
doc="""
.. _top:
Hello world
===========
This is an **emphased text**, some ``interpreted text``.
And this is a reference to top_::
$ print("Hello world")
"""
class RST(App):
def build(self):
return RSTGUI()
if __name__=='__main__':
RST().run()
After little research i have figured it out, Alhumdolillah.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.rst import RstDocument
from kivy.uix.popup import Popup
Builder.load_string("""
<RSTGUI>:
MyRST:
text: root.doc
<about_us_popup>:
size_hint: 0.8,0.6
title: "About Us"
BoxLayout:
orientation:'vertical'
RstDocument:
text: "MyText"
Button:
text:"OK"
size_hint: 1,0.1
on_press:root.dismiss()
""")
class RSTGUI(BoxLayout):
doc="""
.. _top:
Hello world
===========
This is an **emphased text**, some ``interpreted text``.
And this is a reference to top_::
$ print("Hello world")
"""
class MyRST(RstDocument):
def __init__(self, **kwargs):
super(MyRST, self).__init__(**kwargs)
self.about_us_popup = about_us_popup()
def goto(self, ref, *largs):
self.about_us_popup.open()
class RST(App):
def build(self):
return RSTGUI()
class about_us_popup(Popup):
pass
if __name__=='__main__':
RST().run()

Kivy's ScreenManager and Popups don't want to work together

as stated in the title - I'm stuck. I've been playing with the code around and everything works as long as I keep ScreenManager and Popup separate. Once combined - they refuse to cooperate. Anyway, here is the simple app that shows the problem I'm having.
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import Screen, ScreenManager
class First(GridLayout,Screen):
def show_popup(self):
Popp().open()
pass
class Second(Screen):
pass
class Popp(Popup):
pass
class ScreenManagement(ScreenManager):
pass
app = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return app
if __name__ == "__main__":
MainApp().run()
And main.kv file
ScreenManagement:
First:
Second:
<First>:
name:"First"
rows: 2
Button:
text: "FirstButton"
on_release: app.root.current = "Second"
Button:
text: "Show popup"
on_release: root.show_popup()
<Second>:
name:"Second"
Button:
text: "BUTTON"
on_release: app.root.current = "First"
<Popp>:
title: "testing"
text: "Hello world"
size_hint: None,None
size: 400,400
auto_dismiss: False
Button:
text: "Okay"
on_press: root.dismiss()
App starts, first and second screen are working but when trying to get popup up I end up with:
kivy.uix.popup.PopupException: Popup can have only one widget as content
Somehow Screen is seen as a widget inside of Popp? Or am I terribly misinterpreting kivy docs?
It's a bug with loading kv file, it should throw an exception in this case.
What you are doing in the code is loading the kv file twice, what causes some weird behavior. Just delete the Builder.load_file(..) and it will work. The file is going to be loaded automatically.
Also, never do double subclassing of widgets like class First(GridLayout, Screen) as it might lead to some problems. Instead, create a grid layout inside the screen.
Put the elements in the Popup inside a layout, for example: Boxlayout.
Here's what I mean:
<Popp>:
title: "testing"
BoxLayout:
orientation: 'vertical'
size_hint: None,None
size: 400,400
Label:
text: "Hello world"
Button:
text: "Okay"
on_press: root.dismiss()
I have same problem with using kivy Builder.load_file and Popup, they dont work together.
the solution is simple, build popup in python code side. this is a loading popup example:
python:
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
import time, threading
buildKV = Builder.load_file("example.kv")
class ExampleApp(App):
def show_popup(self):
content = BoxLayout(orientation= "vertical")
image=Image(source= 'files/loading.gif', anim_delay= 0)
label=Label(text= 'Model is Running.\nBe Patient Please.')
content.add_widget(image)
content.add_widget(label)
self.popup = Popup(title='Model is Running.',
size_hint=(.250, .785),
content=content, auto_dismiss=False)
self.popup.open()
def process_button_click(self):
# Open the pop up
self.show_popup()
# Call some method that may take a while to run.
# I'm using a thread to simulate this
mythread = threading.Thread(target=self.something_that_takes_5_seconds_to_run)
mythread.start()
def something_that_takes_5_seconds_to_run(self):
thistime = time.time()
while thistime + 10 > time.time(): # 5 seconds
time.sleep(1)
# Once the long running task is done, close the pop up.
self.pop_up.dismiss()
if __name__ == "__main__":
ExampleApp().run()
kivy:
BoxLayout:
Button:
height: 40
width: 100
size_hint: (None, None)
text: 'Click Me'
on_press: app.process_button_click()

Categories