Reputation: 43
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?
Upvotes: 4
Views: 2933
Reputation: 14794
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()
Upvotes: 3
Reputation: 24812
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.
Upvotes: 0