Home > Blockchain >  Understand Kivy order-of-creation, changing root widget results in crash
Understand Kivy order-of-creation, changing root widget results in crash

Time:04-11

Using a Screenmanager as the root widget, I have some simple code to (optionaly) display a Splashscreen, then the Mainscreen. It works fine. But I would like to alter it, so I have a Boxlayout as the root widget insted (so I can display an ever-present status-bar, above the ScreenManager). However when I add the Boxlayout, it crashes, I suspect due to an order-of-creation issue that is beyond my understanding.

This code works:

class MainScreenManager(ScreenManager):
    def __init__(self, **kwargs):
        super(MainScreenManager, self).__init__(**kwargs)
        if not SHOW_SPLASH_SCREEN
            self.transition = NoTransition()
            self.current = 'screen_main'
            self.transition = FadeTransition()
        else:
            #close the splash screen after 3 seconds
            Clock.schedule_once(partial(self.changescreen, 'screen_main'),3)
    
    def changescreen(self, newscreen, *largs):
        self.current = newscreen
    
class Screen_Splash(Screen):
    pass

class Screen_Main(Screen):
    pass

class TestApp(App):
    def build(self):
        mainprogram=MainScreenManager()
        return mainprogram

if __name__ == '__main__':
    TestApp().run()

test.kv

<MainScreenManager>:
    id: MainScreenManager
    Screen_Splash:
    Screen_Main:

<Screen_Splash>:
    name: 'screen_splash'
    FloatLayout:
        Image:
            source: 'resources/splashscreen.png'
            keep_ratio: True

<Screen_Main>:
    name: 'screen_main'
    Image:
        source: 'resources/1_mainscreen.png'
        keep_ratio: True

However, if I try and add a BoxWidget as the parent of the ScreenManager, it will work ONLY if I allow the SplashScreen to be shown, but if I set SHOW_SPLASH_SCREEN=False, then it will crash on the line self.current = 'screen_main' with the following error:

self.current = 'screen_main'

  File "kivy/properties.pyx", line 520, in kivy.properties.Property.__set__
  File "kivy/properties.pyx", line 567, in kivy.properties.Property.set
  File "kivy/properties.pyx", line 606, in kivy.properties.Property._dispatch
  File "kivy/_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
  File "kivy/_event.pyx", line 1213, in kivy._event.EventObservers._dispatch
  File "/usr/lib/python3/dist-packages/kivy/uix/screenmanager.py", line 1052, in on_current
    screen = self.get_screen(value)

  File "/usr/lib/python3/dist-packages/kivy/uix/screenmanager.py", line 1078, in get_screen
    raise ScreenManagerException('No Screen with name "%s".' % name)

I added some debugs, I can see that "screen_main" is created before this error occurs - so I suspect maybe that Screen has been created, but not yet actually added to the ScreenManager? Perhaps that is the cause of the bug? If so, how would I fix that?

Here is the code that causes the crash: (now MasterBoxLayout is the new root node)

class MasterBoxLayout(BoxLayout):
    #this is now the new Root Node
    pass

class MainScreenManager(ScreenManager):
    def __init__(self, **kwargs):
        super(MainScreenManager, self).__init__(**kwargs)
        if not SHOW_SPLASH_SCREEN
            self.transition = NoTransition()
            self.current = 'screen_main'
            self.transition = FadeTransition()
        else:
            Clock.schedule_once(partial(self.changescreen, 'screen_main'),3)
    
    def changescreen(self, newscreen, *largs):
        self.current = newscreen
    
class Screen_Splash(Screen):
    pass

class Screen_Main(Screen):
    pass

class TestApp(App):
    def build(self):
        mainprogram=MasterBoxLayout()
        return mainprogram

if __name__ == '__main__':
    TestApp().run()

test.ky is the same, except for the addition of a BoxLayout:

<MasterBoxLayout>:
    #this should be the new root of the tree
    orientation: 'vertical'
    MainScreenManager:

<MainScreenManager>:
    id: MainScreenManager
    Screen_Splash:
    Screen_Main:

<Screen_Splash>:
    name: 'screen_splash'
    FloatLayout:
        Image:
            source: 'resources/splashscreen.png'
            keep_ratio: True

<Screen_Main>:
    name: 'screen_main'
    Image:
        source: 'resources/1_mainscreen.png'
        keep_ratio: True

EDIT: Note, I am new to Kivy, if there are any general comments on the way I am doing this...and suggestions for a better method, that would be greatfully received.

CodePudding user response:

The problem is that you are doing this in the __init__() method of your MainScreenManager. At that point, the Screens have not yet been created. You can just move that logic into an on_kv_post() method, so that the code is not run until the kv rules are executed:

class MainScreenManager(ScreenManager):

    def on_kv_post(self, base_widget):
        if not SHOW_SPLASH_SCREEN:
            self.transition = NoTransition()
            self.current = 'screen_main'
            self.transition = FadeTransition()
        else:
            Clock.schedule_once(partial(self.changescreen, 'screen_main'), 3)

    def changescreen(self, newscreen, *largs):
        self.current = newscreen
  • Related