I'm trying to get an application I've been building to stop at a certain point, and run a cleanup procedure on stop. This seems like it should be easy, but I keep encountering an error, and I haven't been able to track down a solution.
I use kivy 1.8.0 and Python 3.3. For ease, I've modified some code from the kivy documentation, since my code is based on the same framework, and both give me exactly the same error:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# Create both screens. Please note the root.manager.current: this is how
# you can control the ScreenManager from kv. Each screen has by default a
# property manager that gives you the instance of the ScreenManager used.
Builder.load_string("""
<MenuScreen>:
BoxLayout:
Button:
text: 'Goto settings'
on_press: root.manager.current = 'settings'
Button:
text: 'Quit'
on_press: root.exit()
<SettingsScreen>:
BoxLayout:
Button:
text: 'My settings button'
Button:
text: 'Back to menu'
on_press: root.manager.current = 'menu'
""")
# Declare both screens
class MenuScreen(Screen):
def exit(self):
App.stop(self)
class SettingsScreen(Screen):
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class TestApp(App):
def build(self):
return sm
def on_stop(self):
print('leaving now') # this is where I'd want to run the end of program procedure
if __name__ == '__main__':
TestApp().run()
When I run this and click on the Quit button, I get the following error:
builtins.KeyError: 'on_stop'
I should also note that if I comment out the on_stop function in class TestApp, the error persists. Any idea what is going wrong?
You're passing an instance of MenuScreen to App.stop(), which is causing your error as it expects an App instance. You can retrieve the running app and stop it like so:
class MenuScreen(Screen):
def exit(self):
App.get_running_app().stop()
Here is a solution for your problem:
# Declare both screens
class MenuScreen(Screen):
def exit(self):
self.manager.app.stop()
class TestApp(App):
def build(self):
# Create the screen manager
sm = ScreenManager()
# monkey patch the screen manager instance to add the app
sm.app = self
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
return sm
though I do not think this is the canonical solution for your problem, as it is here involving monkey patching.
But what's wrong is that the stop() method you want to call exists only for an app instance, though the way you were handling it, you couldn't have access to the running App instance as it was declared after the code needing it.
So the solution is to move the ScreenManager build up inside the build method. And that's where I think my solution is not a good design: I'm basically adding a member to the ScreenManager by monkeypatching the instance, to give it a link to the App instance.
There you can call the close() function, and close your application gracefully.
Related
Goal is to move to a settings screen when no button is pressed, text is entered or whatever for a certain time.
In fact, functionality is like a screensaver of some sorts.
code version 1
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
sm.switch_to(setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return sm
if __name__ == "__main__":
wiscApp().run()
and the .kv
<MenuScreen>:
BoxLayout:
orientation: 'vertical'
BoxLayout:
Button:
text: "resettimeout"
on_press: app.resetscreensavertimeout()
Button:
text: "do other things"
Button:
text: 'settings'
on_press: root.manager.current = 'settings'
<SettingsScreen>:
BoxLayout:
Button:
text: "stop app"
on_press: app.stop()
Button:
text: 'Back to menu'
on_press: root.manager.current = 'menu'
This works perfectly up until the calling of sm.switch_to(setscreen) in the setscreensaver function.
I tried the following:
code version 2
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
But then the settings screen is BLANK!
In the first version of the code, I understand by it doens't work: both sm and setscreen are undefined variables in that function.
In the second version, I don't understand why the settings screen is blank.
edit
*** version 3 of the code***
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
In this version 3, transitioning to the settings screen with the signal works fine, but if I then click the menu button, I get this error (this error does not appear in the other versions of the code):
kivy.uix.screenmanager.ScreenManagerException: No Screen with name "menu".
So, I have several questions
how do I reset the timer every time a button is pressed, text is
entered of whatever, other than defining callbacks for every event
(e.g on_press: app.resetscreensavertimeout()) in the .kv code)?
How do I switch to the settings screen in the first version of the
code?
Why is the settings screen blank in code version 2?
Why does the error occur in version 3?
Is there another (better) way to code this?
thanks a lot!
Here is a modified version of your code that uses Clock.schedule_once() instead of signal:
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
self.resetscreensavertimeout()
self.sm.current = 'settings'
def resetscreensavertimeout(self, *args):
print("resetting screensaver timer")
self.resetEvent.cancel()
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
Window.bind(on_touch_down=self.resetscreensavertimeout)
Window.bind(on_key_down=self.resetscreensavertimeout)
return self.sm
This also uses Window.bind() to trigger the reset of the timeout whenever a button is pressed or a key is pressed.
I took this one step further and defined a general-purpose function to add this functionality to any app.
from typing import Callable
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
def add_inactivity_timeout(app: App, do_timeout: Callable, inactivity_minutes=60):
"""
Sets up an inactivity timeout for the given Kivy app. "Activity" meaning
a touch (a button click) or a key press.
IMPORTANT: Call this from within the `build()` method, not `__init__()`.
This adds a `reset_timeout()` method to the app which can be called
directly by any other activity that needs to be considered (e.g.
periodically within a long-running computation, or while accessing an
external resource).
Alternatively, call `cancel_timeout()` to disable the timeout on the
way in to the long-running activity, and then call `reset_timeout()`
afterwards to re-enable it.
:param app: The kivy app to enhance.
:param do_timeout: The method to call if a timeout occurs.
:param inactivity_minutes: The timeout length in minutes. Defaults to 60.
"""
app.inactivity_minutes = inactivity_minutes
app.reset_event = None
def cancel_timeout(*args):
if app.reset_event:
app.reset_event.cancel()
def reset_timeout(*args):
cancel_timeout()
app.reset_event = Clock.schedule_once(do_timeout, app.inactivity_minutes * 60)
app.cancel_timeout = cancel_timeout
app.reset_timeout = reset_timeout
reset_timeout()
Window.bind(on_touch_down=reset_timeout)
Window.bind(on_key_down=reset_timeout)
To use it, create a callback method for the timeout:
def inactive(self, *args):
print("Timed out -- Inactive!")
self.stop()
and then call
add_inactivity_timeout(self, self.inactive, inactivity_minutes=15)
from within build().
When i run the file, the buttons show up. Everything looks fine, until i click button 1 for the method Main.c() to get called. the interpreter spits out an exception claiming i haven't defined it yet, when i have. I cannot find a solution anywhere.
the .py file:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.lang.builder import Builder
kivy.require("1.11.1")
class Main:
#staticmethod
def c():
print("c")
class Start(App):
def setup(self):
Builder.load_file('start.kv')
Start().run()
the .kv file:
BoxLayout:
Button:
id: btn1
text: "Button 1"
on_press: Main.c()
Button:
text: "Button 2"
on_press: print(8 * 8)
There are many problems with your code, but, one way to do what you ask for is to
Create an instance of your Main class in your App class: self.main = Main()
Call this instance from your kv code: on_press: app.main.c()
All the tutorials in the internet show how to use the ScreenManager and change different screens from the .kv files.
I am still learning and I can not manage to control it from the .py file.
I want my app first to perform some validation before starting. There fore i want Kivy app to start with a simple "Please wait" screen, and after that to go to the Main screen.
Please help!
Here is a short example of how one can change a screen from the python code
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
Builder.load_string("""
<Screen1>:
name: 'screen1'
Button:
on_press: root.on_click()
<Screen2>:
name: 'screen2'
Label:
text: "Cool!"
<MyScreenManager>:
Screen1:
Screen2:
""")
class Screen1(Screen):
def on_click(self):
sm.current = 'screen2'
class Screen2(Screen):
pass
class MyScreenManager(ScreenManager):
pass
class SimpleApp(App):
def build(self):
global sm
sm = MyScreenManager()
return sm
SimpleApp().run()
I have used the button on_press event to call the python code but you can run your validations in another thread and when your are done you can call back to the kivy code using the Clock api
I'm trying to get my head around screenmanager, in particular referencing objects within.
I used to use this to set a value:
class Widgets(Widget)
pass
w = Widgets()
w.ids.MyTitle.text = 'something'
Now I have this:
class Widgets(Screen)
pass
class SettingsScreen(Screen)
pass
sm = ScreenManager()
sm.add_widget(Widgets(name='main'))
sm.add_widget(SettingsScreen(name='settings'))
How do I reference MyTitle now? I've tried all sorts of combos such as:
sm.ids.main.MyTitle.text =
sm.main.MyTitle.text =
sm.main.ids.MyTitle.text =
.... but not getting it! Can someone put me out of my misery? Is there an easy way of browsing through the sm object or iterating through it maybe?
EDIT: Adding minimal running version:
minimal.kv:
# File name: minimal.py
#:kivy 1.8.0
<Widgets>
Button:
id: MyTitle
text: 'hello'
<SettingsScreen>:
Button:
id: Other
text: 'settings'
minimal.py:
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.app import App
class Widgets(Screen):
pass
class SettingsScreen(Screen):
pass
class myApp(App):
def build(self):
return sm
def on_start(self):
global sm
sm.ids['main'].ids['MyTitle'].text = 'changed' # <-- this fails
Builder.load_file("minimal.kv")
sm = ScreenManager()
sm.add_widget(Widgets(name='main'))
sm.add_widget(SettingsScreen(name='settings'))
if __name__ == '__main__':
myApp().run()
To get a screen from ScreenManager, use get_screen:
sm.get_screen('main').ids.MyTitle.text = 'changed'
Also, you may construct your app so:
kv file:
# File name: minimal.py
#:kivy 1.8.0
ScreenManager:
Widgets:
name: 'main'
SettingsScreen:
name: 'settings'
<Widgets>:
Button:
id: MyTitle
text: 'hello'
<SettingsScreen>:
Button:
id: Other
text: 'settings'
and in the python file:
sm=Builder.load_file(..)
class my12App(App):
def build(self):
return sm
def on_start(self):
self.root.get_screen('main').ids.MyTitle.text = 'changed'
According to the documentation you access an id like you would any dictionary key:
widget.ids['MyTitle']
Because ScreenManager in itself derives from Widget, and a given widget maintains a list of widgets it is aware of, you probably need something like:
sm.ids[0].ids['MyTitle'].text
However this is hard to say without a Minimal, Complete, and Verifiable Example. One thing you could do is:
for id in sm.ids: # Iterate through all widgets in ids
print(id) # Get the string representation of that widget
As a side note, this:
class Widgets(Screen)
pass
... will probably cause confusion, because you're extending Widget with Widgets (via an intermediate class Screen). OOP suggests that a subclass of a class should be a more specific form of the class. So, a Screen is a type of Widget. But Widgets is really some number of Widgets.
Unfortunately neither the code in the question, nor the one in the accepted answer worked for me, and I'm unsure as to how exactly it is supposed to work. My interpretation of it is, that OP wanted to change a property of a widget (a Button) inside of one Screen based on a callback from another Screen. The below code is a complete MWE doing that:
# File name: minimal.py
#:kivy 2.0.0
ScreenManager:
FoobarScreen:
name: 'foobar'
Widgets:
name: 'widgets'
<FoobarScreen>:
Button:
id: lalala
text: 'lalala'
<Widgets>:
Button:
id: lololo
text: 'lololo'
(minimal.kv)
import kivy
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.app import App
class FoobarScreen(Screen):
pass
class Widgets(Screen):
pass
class myApp(App):
def build(self):
return sm
def on_start(self):
self.root.get_screen('widgets').ids.lololo.text = 'changed'
self.root.current = 'widgets'
if __name__ == '__main__':
sm=Builder.load_file('minimal.kv')
myApp().run()
(minimal.py)
I'm doing a proyect using kivy but i have a problem with the checkboxes. At first I'm trying to do the program like python coding (I know it is'nt clean, but I understand more) And i have a first screen with this coding:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.checkbox import CheckBox
class MainScreen(GridLayout):
def __init__(self,**kwargs):
e=[]
super(MainScreen, self).__init__(**kwargs)
self.cols=2
def on_checkbox_active(checkbox, value):
if value:
e.append(value)
print e
else:
print('The checkbox', checkbox, 'is inactive')
self.add_widget(Label(text='Inserta assignatures desitjades',font_size=35))
self.add_widget(Label(text=''))
ch1 = CheckBox()
self.add_widget(ch1)
self.add_widget(Label(text='Termotecnia'))
ch2 = CheckBox()
self.add_widget(ch2)
self.add_widget(Label(text='Termotecnia'))
ch3 = CheckBox()
self.add_widget(ch3)
self.add_widget(Label(text='Termotecnia'))
ch4 = CheckBox()
self.add_widget(ch4)
self.add_widget(Label(text='Termotecnia'))
b1=Button(text='Exit',background_color=[0.7,0.7,1,1],font_size=24)
self.add_widget(b1)
b2=Button(text='Next',font_size=24,font_color=[1,3,4,0],background_color=[1,2,3,6])
self.add_widget(b2)
ch1.bind(active=on_checkbox_active)
ch2.bind(active=on_checkbox_active)
b1.bind(on_press=exit)
b2.bind(on_press=reloaded)
...
class SimpleKivy(App):
def build(self):
return MainScreen()
if __name__=='__main__':
SimpleKivy().run()
I want to select two or three options for example, and save it for the next screen, like a type of selection. If anyone knows how to do it and save information for the next screen it woul help me a lot, because i have the code of the next screen for all the options, but i want to preselect in the first screen and then only use which i have selected. Also if anyone can help me, i want to know hoy to do the transition to another class (screen) when the button "Next" is pressed. I know this question are pretty simple but I'm new in kivy programming and some concepts are pretty difficult. Thanks.
What you want is accessing variables in other classes. Sometimes this can be annoying and you can do it either hard way with all __init__() and stuff, or... a simplier way comes along: it's get_running_app().
You can create a dictionary or something else, where you can store any value your other classes need to access. It's similar to using globals and it costs you less lines of code. For example in your case you could use a dictionary(or nested dictionaries, json, ...) to store for example 'checkboxes':'<names of checked ones>' and in each init you can loop over these values to make checkboxes active
Basically all you need is a = App.get_running_app() somewhere and something to access in main - App - class.
Example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string('''
<Root>:
MainScreen:
name: 'main'
AnotherScreen:
name: 'another'
<MainScreen>:
BoxLayout:
Button:
text: 'next screen'
on_release: root.parent.current='another'
Button:
text: 'ping!'
on_release: root.ping()
<AnotherScreen>:
BoxLayout:
Button:
text: 'previous screen'
on_release: root.parent.current='main'
Button:
text: 'ping!'
on_release: root.ping()
''')
class MainScreen(Screen):
def __init__(self, **kw):
super(MainScreen, self).__init__(**kw)
self.a = App.get_running_app()
def ping(self):
print self.a.big_dict['hi']
class AnotherScreen(Screen):
def ping(self):
b = App.get_running_app()
print b.big_dict['hi']
class Root(ScreenManager):
pass
class SimpleKivy(App):
big_dict={'hi':'hi there!'}
def build(self):
return Root()
SimpleKivy().run()
You can see there's no need to call __init__(), no need to write more lines of code if you really don't need to.