user3661759
user3661759

Reputation: 43

KeyError stopping App in kivy

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

Answers (2)

kitti
kitti

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

zmo
zmo

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

Related Questions