1

I want to access root widgets ids from other rootwidgets, but I can't seem to fully grasp how referencing works in Kivy and using a ScreenManager with different screens makes it even harder for me.

I want to achieve the following: Reference between Kivy widgets

Edit: single file version

(This code assumes you're going to build a complex app, so I don't want to load all code at startup. Hence the kv_strings are loaded when switching screen, and not put into kv code of the ScreenManager. Code is based on the Kivy Showcase.)

Code main.py, Edit 2: working code (see answer why)

#!/usr/bin/kivy
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.properties import StringProperty, ObjectProperty


kv_foo = '''
<FooScreen>:
    id: fooscreen_id

    BoxLayout:
        id: content
        orientation: 'vertical'
        spacing: '20dp'
        padding: '8dp'
        size_hint: (1, 1)

BoxLayout:
    orientation: 'vertical'

    Label:
        id: important_text
        size_hint_y: 0.3
        text: app.imp_text

    Button:
        id: magic_change
        size_hint_y: 0.3
        text: "Change text above to text below (after screen switch)"
        on_press: app.change_text()

    ScreenManager:
        id: sm
        on_current_screen:
            idx = app.screen_names.index(args[1].name)
'''


class FooScreen(Screen):
    # 'content' refers to the id of the BoxLayout in FooScreen in foo.kv
    def add_widget(self, *args):
        if 'content' in self.ids:
            return self.ids.content.add_widget(*args)
        return super(FooScreen, self).add_widget(*args)


class FooApp(App):
    imp_text = StringProperty("Should change to text from id: magic_text")
    screen_magic = ObjectProperty()
    magic_layout = ObjectProperty()

    def build(self):
        self.title = 'Foo'
        self.root = root = Builder.load_string(kv_foo)

        # Trying stuff with References
        self.sm = self.root.ids.sm  # ScreenManager

        # Setting up screens for screen manager
        self.screens = {}
        self.available_screens = [kv_mainmenu, kv_magic]
        self.screen_names = ['MainMenu', 'Magic']
        self.go_screen(0)

    # Go to other screen
    def go_screen(self, idx):
        print("Change MainScreen to: {}".format(idx))
        self.index = idx
        # Go to not main menu
        if idx == 0:
            self.root.ids.sm.switch_to(self.load_screen(idx), direction='right')
        # Go to main menu
        else:
            self.root.ids.sm.switch_to(self.load_screen(idx), direction='left')

    # Load kv files
    def load_screen(self, index):
        if index in self.screens:
            return self.screens[index]
        screen = Builder.load_string(self.available_screens[index])
        self.screens[index] = screen
        # if index refers to 'Magic' (kv_magic), create reference
        if index == 1:
            Clock.schedule_once(lambda dt: self.create_reference())
        return screen

    # Trying to get id's
    def create_reference(self):
        print("\nrefs:")
        # Get screen from ScreenManager
        self.screen_magic = self.sm.get_screen(self.screen_names[1])
        # screen.boxlayout.magiclayout
        self.magic_layout = self.screen_magic.children[0].children[0]

    def change_text(self):
        # Get text from id: magic_text
        if self.magic_layout:
            self.imp_text = self.magic_layout.ids['magic_text'].text


kv_mainmenu = '''
FooScreen:
    id: mainm
    name: 'MainMenu'

    Button:
        text: 'Magic'
        on_release: app.go_screen(1)
'''

kv_magic = '''
<MagicLayout>
    id: magic_layout
    orientation: 'vertical'

    Label:
        id: magic_text
        text: root.m_text

FooScreen:
    id: magic_screen
    name: 'Magic'

    MagicLayout:
        id: testmagic
'''


class MagicLayout(BoxLayout):
    m_text = StringProperty("Reference between widgets test")


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

Question

How can I set up proper references that the button "Change text above..." can retrieve magic_text.text ("Reference between widgets test") and change self.imp_text to magic_text.text?

Community
  • 1
  • 1
NumesSanguis
  • 5,832
  • 6
  • 41
  • 76
  • Your code is horribly messy, no wonders you have a problem with such easy task. Add screens to screen manager in kv, and reference them with ids. – jligeza Mar 07 '16 at 09:06
  • @jiligeza, how is the code messy? This example assumes you're going to make a big program, so putting all Screens already in the ScreenManager in the .kv defeats the purpose of not having to load all code in your app. – NumesSanguis Mar 07 '16 at 10:03
  • I don't understand why the kv must be split. It is designed to be modular on its own. Anyway if you'd like to reference a widgets globally you might take a look what I've came up with: http://stackoverflow.com/questions/35792621/kivy-get-widgets-ids-and-accessing-widgets-by-unique-property – kilbee Mar 07 '16 at 17:15
  • @kilbee, for this example it is indeed not necessary to split, but when you make a bigger app, you don't want all code in 1 .py file and 1 .kv file and also not that all widgets in your app are rendered on startup (10 sec for app start is a bit long). Here I tried to set the bare bone of such a bigger app while still having access to all widgets through referencing (see answer). – NumesSanguis Mar 07 '16 at 18:41

1 Answers1

2

I found a way to reference to Kivy widgets not loaded at the startup of the app without using globals. Thanks @inclement for ScreenManager.get_screen().

I had to add the following code:

class FooApp(App):
    screen_magic = ObjectProperty()
    magic_layout = ObjectProperty()

    ...

    # Trying to get id's
    def create_reference(self):
        print("\nrefs:")
        # Get screen from ScreenManager
        self.screen_magic = self.sm.get_screen(self.screen_names[1])
        # screen.boxlayout.magiclayout
        self.magic_layout = self.screen_magic.children[0].children[0]

    def change_text(self):
        # Get text from id: magic_text
        if self.magic_layout:
            self.imp_text = self.magic_layout.ids['magic_text'].text

self.screen_magic is assigned the screen I need (<FooScreen>) and self.magic_layout is assigned the widget I need (<MagicLayout>). Then I can use the ids from <MagicLayout> to access the Label magic_text's text.

(For full code see updated question)

NumesSanguis
  • 5,832
  • 6
  • 41
  • 76