Search code examples
pythonkivy

Kivy: Set text attribute inside Screen class


I am writing a Kivy project where my code will dynamically set the text property within a Screen class. Below is a MRE that recreates the problem, but my real code is much more complicated.

When I first wrote it, I tried something like self.ids.my_label.text = 'This no worky' but that threw an error:

AttributeError: 'super' object has no attribute 'getattr'

I did a print(self.ids) and it is empty. Based on some other SO posts (such as here) I added the line s = self.root.get_screen('screen_one') but that also throws an error:

AttributeError: 'ScreenOne' object has no attribute 'root'

I'm assuming the problem is that I'm calling the ids within the screen class, whereas every example I am finding is doing it from some other spot. I sort of get that calling self.root doesn't work because the ids doesn't live within the root, but every combo I could think of also doesn't work. I also tried this solution, but either I didn't implement it right or it isn't my problem. What's the solution?

import kivy
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen

class ScreenOne(Screen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        s = self.root.get_screen('screen_one') 
        s.ids.my_label.text = 'This no worky'

class ScreenTwo(Screen):
    pass
    
class ScreenThree(Screen):
    pass

class MyScreenMgr(ScreenManager):
    pass

class MultiApp(App):
    def build(self):
        return MyScreenMgr()
 
sample_app = MultiApp()
sample_app.run()

And multi.kv:

#:import NoTransition kivy.uix.screenmanager.NoTransition

<MyScreenMgr>:
    transition: NoTransition()
    ScreenOne:
    ScreenTwo:
    ScreenThree:

<ScreenOne>:
    name:'screen_one'
    BoxLayout:
        Label:
            id: my_label 
        Button:
            text: "Go to Screen 2"
            background_color : 0, 0, 1, 1
            on_press:
                root.manager.current = 'screen_two'
  
<ScreenTwo>:
    name:'screen_two'
    BoxLayout:
        Button:
            text: "Go to Screen 3"
            background_color : 1, 1, 0, 1
            on_press:
                root.manager.current = 'screen_three'
 
<ScreenThree>:
    name:'screen_three'
    BoxLayout:
        Button:
            text: "Go to Screen 1"
            background_color : 1, 0, 1, 1
            on_press:
                root.manager.current = 'screen_one'

Solution

  • The problem is that the ids of a widget are not set until after __init__() is executed. So the solution is to delay using the ids. Here is a modified version of your ScreenOne class that does this:

    class ScreenOne(Screen):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            Clock.schedule_once(self.set_text)
    
        def set_text(self, dt):
            self.ids.my_label.text = 'This worky'