How to run a Kivy EventLoop in slave mode? - python

Related question here
I've discovered a slave property for the runTouchApp function that prevents the Kivy's event loop from running and forces to update it from somewhere else.
Here's a part of app.py where that property is used:
# we are in a slave mode, don't do dispatching.
if slave:
return
try:
if EventLoop.window is None:
_run_mainloop()
else:
EventLoop.window.mainloop()
finally:
stopTouchApp()
Here, if the app is not being run in slave mode, we have two choices on how to run the mainloop.
The first one, _run_mainloop() function works pretty straight-forward - it simply calls the EventLoop.run(), which in turn infinitely calls EventLoop.idle().
That could lead us to believe that to keep the GUI running, we only need to call idle.
But then there's the second option, which calls the kivy.core.window.WindowSDL's method mainloop.
That method works by calling another method, the _mainloop and this is where it gets interesting. The definition of said method is huge and it handles all sorts of events.
So okay, I ran my app in slave mode:
class TestApp(App):
def start_event(self):
pass
def build(self):
return Button(text = "hello")
def run(self):
# This definition is copied from the superclass
# except for the start_event call and slave set to True
if not self.built:
self.load_config()
self.load_kv(filename=self.kv_file)
root = self.build()
if root:
self.root = root
if self.root:
Window.add_widget(self.root)
window = EventLoop.window
if window:
self._app_window = window
window.set_title(self.get_application_name())
icon = self.get_application_icon()
if icon:
window.set_icon(icon)
self._install_settings_keys(window)
self.dispatch('on_start')
runTouchApp(slave = True)
self.start_event() # Here we start updating
self.stop()
Now, if I put this in the start_event method (by expectations):
def start_event(self):
while True:
EventLoop.idle()
Guess what, the app doesn't respond to touch events and freezes.
So I tried to call the Window's mainloop instead:
def start_event(self):
EventLoop.window.mainloop()
And suddenly everything started working normally again. But the problem here is that such a call blocks forever, as it is an infinite loop, so there's no one-time update call like EventLoop.idle
How to keep the app running using such one-time calls?

Well, this is Python so assuming you want to stick to WindowSDL provider, you can always monkey patch this mainloop function so it won't be infinite:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.base import EventLoop
Builder.load_string('''
<MyWidget>:
Button:
text: 'test'
on_press: print('doing my job')
''')
# https://github.com/kivy/kivy/blob/master/kivy/core/window/window_sdl2.py#L630
def mainloop(self):
# replaced while with if
if not EventLoop.quit and EventLoop.status == 'started':
try:
self._mainloop()
except EventLoop.BaseException as inst:
# use exception manager first
r = EventLoop.ExceptionManager.handle_exception(inst)
if r == EventLoop.ExceptionManager.RAISE:
EventLoop.stopTouchApp()
raise
else:
pass
class MyWidget(BoxLayout):
pass
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MyWidget(), slave=True)
# monkey patch
EventLoop.window.mainloop = mainloop
while True:
EventLoop.window.mainloop(EventLoop.window)
print('do the other stuff')
if EventLoop.quit:
break
It's really hacky though and thus I'd not recommend running something like that in a production code.

Related

Cannot disable buttons in Kivy (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.

How to run a Method on the exit of a kivy app

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.

Kivy - changing button color on press

I've recently started using kivy to design GUI for my python app. In the app one of the actions is sending requests to server using provided API. After a couple of tests I've noticed one annoying thing that happens only when I want to make a request - the button doesn't change color on press (I'm talking about default action). However, it is changing when simple print() is used.
How I can fix it? Any idea?
This is the code of my test app:
class TestApp(App):
def build(self):
parent = Widget()
btn = Button(text='Add')
btn.bind(on_press=self.add)
parent.add_widget(btn)
return parent
def add(self, obj):
print("Adding")
request = Request(url, urlencode(post_fields).encode())
urlopen(request)
That happened most likely because the UI froze. Theself.add is called, but right after that the UI waits until the request is done, which for you might result in that.
Try to do it like this:
import threading
class TestApp(App):
def build(self):
parent = Widget()
btn = Button(text='Add')
btn.bind(on_press=self.add)
parent.add_widget(btn)
return parent
def add(self, obj):
print("Adding")
#self.printer() # freezing
threading.Thread(target=self.printer).start()
def printer(self, *args):
while True:
print 'blob'
TestApp().run()
Also, instead of on_press use rather on_release. Prevents accidental events if I remember correctly (checks for collision touch↔button area).

Kivy app exiting on screen change

I'm trying to port my code from the .kv file all to Python (it seems easier for me that way). I've got two screens and I've been switching to the next one using root.manager.current = "main" in the kv file. However, when writing this bit in Python code, I've run into the crash of my app. This is the code I have:
class CustomScreenManager(ScreenManager):
def switch_view(self):
self.current = 'main'
class Intro(Screen):
pass
class Chat(Screen):
pass
class ChatApp(App):
def build(self):
Screens = CustomScreenManager(transition = NoTransition())
intro = Intro()
chat = Chat(name = "main")
bt1_intro = Button(on_press = Screens.switch_view())
intro.add_widget(bt1_intro)
Screens.add_widget(intro)
Screens.add_widget(chat)
return Screens
if __name__ == "__main__":
ChatApp().run()
ChatApp().screen_manager
I've also tried the switch_to method but it also crashes the app. What can I do to avoid the crashing and get the expected behaviour? Thanks in advance.
Change the definition of switch_view to
def switch_view(self, *args):
and add the Button with
bt1_intro = Button(on_press = Screens.switch_view)
The app crashes because in your original assignment of bt1_intro switch_view gets called (rather than passed to the function), and at the time the screen does not exist.

How to stop a QDialog from executing while still in the __init__ statement (or immediately after)?

I am wondering how I can go about stopping a dialog from opening if certain conditions are met in its __init__ statement.
The following code tries to call the 'self.close()' function and it does, but (I'm assuming) since the dialog has not yet started its event loop, that it doesn't trigger the close event? So is there another way to close and/or stop the dialog from opening without triggering an event?
Example code:
from PyQt4 import QtCore, QtGui
class dlg_closeInit(QtGui.QDialog):
'''
Close the dialog if a certain condition is met in the __init__ statement
'''
def __init__(self):
QtGui.QDialog.__init__(self)
self.txt_mytext = QtGui.QLineEdit('some text')
self.btn_accept = QtGui.QPushButton('Accept')
self.myLayout = QtGui.QVBoxLayout(self)
self.myLayout.addWidget(self.txt_mytext)
self.myLayout.addWidget(self.btn_accept)
self.setLayout(self.myLayout)
# Connect the button
self.connect(self.btn_accept,QtCore.SIGNAL('clicked()'), self.on_accept)
self.close()
def on_accept(self):
# Get the data...
self.mydata = self.txt_mytext.text()
self.accept()
def get_data(self):
return self.mydata
def closeEvent(self, event):
print 'Closing...'
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
dialog = dlg_closeInit()
if dialog.exec_():
print dialog.get_data()
else:
print "Failed"
The dialog will be run only if exec_ method is called. You should therefore check conditions in the exec_ method and if they are met, run exec_ from QDialog.
Other method is to raise an exception inside the constructor (though I am not sure, it is a good practice; in other languages you generally shouldn't allow such behaviour inside constructor) and catch it outside. If you catch an exception, simply don't run exec_ method.
Remember, that unless you run exec_, you don't need to close the window. The dialog is constructed, but not shown yet.

Categories