7

I've been trying to build my kv language skills from Accessing id/widget of different class from a kivy file (.kv) using Kivy's clock? by working with the information found in Kivy Screen manager reference in kv language. Unfortunately the latter post does not contain a complete working code example so I can't understand how to make changes to the text elements in a on a specific screen in a multi-screen Kivy application.

After searching much of the day I can't find any simple concrete working examples of how to build a multi-screen app in kv language so here I am. I don't seem to be able to set up the proper references to the individual screens so I can change them.

In the simple example code below I have built a four screen application that switches automatically between four screens. There are 2 things I'd like to learn from this question;

  1. The kv language code that would set-up as much of the screenmanager as possible. ie. can lines 43 thru 47 be reduced or eliminated by the kv language?

  2. The actual python code (which I believe would go on line 56 of the app) that changes the text on the first screen to "Hi I'm The Fifth Screen" prior to it displayed the second time.

Code below. Thanks in advance. ....brad....

import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager, Screen

Builder.load_string("""
<FirstScreen>:
    name: '_first_screen_'
    Label:
        id: first_screen
        text: "Hi I'm The First Screen"
<SecondScreen>:
    name: '_second_screen_'
    Label:
        id: second_screen
        text: "Hi I'm The Second Screen"
<ThirdScreen>:
    name: '_third_screen_'
    Label:
        id: third_screen
        text: "Hi I'm The Third Screen"
<FourthScreen>:
    name: '_fourth_screen_'
    Label:
        id: fourth_screen
        text: "Hi I'm The Fourth Screen"
""")

class FirstScreen(Screen):
    pass

class SecondScreen(Screen):
    pass

class ThirdScreen(Screen):
    pass

class FourthScreen(Screen):
    pass

sm = ScreenManager()
sm.add_widget(FirstScreen())
sm.add_widget(SecondScreen())
sm.add_widget(ThirdScreen())
sm.add_widget(FourthScreen())

class SwitchingScreenApp(App):

    def build(self):
        Clock.schedule_once(self.screen_switch_one, 2)
        Clock.schedule_once(self.screen_switch_two, 4)
        Clock.schedule_once(self.screen_switch_three, 6)
        Clock.schedule_once(self.screen_switch_four, 8)
        # Want to place the code here that changes the first_screen text to "Hi I'm The Fifth Screen"
        Clock.schedule_once(self.screen_switch_one, 10)
        return sm

    def screen_switch_one(a,b):
        sm.current = '_first_screen_'
    def screen_switch_two(a,b):
        sm.current = '_second_screen_'
    def screen_switch_three(a,b):
        sm.current = '_third_screen_'
    def screen_switch_four(a,b):
        sm.current = '_fourth_screen_'

SwitchingScreenApp().run()
Brad Fortner
  • 134
  • 1
  • 2
  • 9

6 Answers6

10

Firstly, I would suggest splitting your codes into Python and kv file, because as your program grows, it will be easier to maintain the UIs/widgets and the Python code. Please refer to the example (app_with_kv.py, switchingscreen.kv, output) for details.

Question: 1

The kv language code that would set-up as much of the screenmanager as possible. ie. can lines 43 thru 47 be reduced or eliminated by the kv language?

sm = ScreenManager()
sm.add_widget(FirstScreen())
sm.add_widget(SecondScreen())
sm.add_widget(ThirdScreen())
sm.add_widget(FourthScreen())

Answer

Step 1

Add a class MyScreenManager into your Python code.

class MyScreenManager(ScreenManager):
    pass
...
    def build(self):
        sm = MyScreenManager()

Step2

Addd the following kv rule either inline or in a separate kv file. This is equivalent to using the statement, sm.add_widget() four times.

<MyScreenManager>:
    FirstScreen:
    SecondScreen:
    ThirdScreen:
    FourthScreen:

Question: 2

The actual python code (which I believe would go on line 56 of the app) that changes the text on the first screen to "Hi I'm The Fifth Screen" prior to it displayed the second time.

Answer

You have to place the following code in any of the 3 switching screen methods excluding the first switching screen method, screen_switch_one()

self.ids.first_screen.ids.first_screen_label.text = "Hi I'm The Fifth Screen"

Example

app_with_kv.py

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


class FirstScreen(Screen):
    pass


class SecondScreen(Screen):
    pass


class ThirdScreen(Screen):
    pass


class FourthScreen(Screen):
    pass


class MyScreenManager(ScreenManager):

    def __init__(self, **kwargs):
        super(MyScreenManager, self).__init__(**kwargs)
        Clock.schedule_once(self.screen_switch_one, 2)

    def screen_switch_one(self, dt):
        self.current = '_first_screen_'
        Clock.schedule_once(self.screen_switch_two, 2)

    def screen_switch_two(self, dt):
        self.current = '_second_screen_'
        self.ids.first_screen.ids.first_screen_label.text = "Hi I'm The Fifth Screen"
        Clock.schedule_once(self.screen_switch_three, 2)

    def screen_switch_three(self, dt):
        self.current = '_third_screen_'
        Clock.schedule_once(self.screen_switch_four, 2)

    def screen_switch_four(self, dt):
        self.current = '_fourth_screen_'
        Clock.schedule_once(self.screen_switch_one, 2)


class SwitchingScreenApp(App):

    def build(self):
        return MyScreenManager()


if __name__ == "__main__":
    SwitchingScreenApp().run()

switchingscreen.kv

#:kivy 1.10.0

<MyScreenManager>:
    FirstScreen:
        id: first_screen
    SecondScreen:
    ThirdScreen:
    FourthScreen:

<FirstScreen>:
    name: '_first_screen_'
    Label:
        id: first_screen_label
        text: "Hi I'm The First Screen"

<SecondScreen>:
    name: '_second_screen_'
    Label:
        id: second_screen_label
        text: "Hi I'm The Second Screen"

<ThirdScreen>:
    name: '_third_screen_'
    Label:
        id: third_screen_label
        text: "Hi I'm The Third Screen"

<FourthScreen>:
    name: '_fourth_screen_'
    Label:
        id: fourth_screen_label
        text: "Hi I'm The Fourth Screen"

Output

enter image description here

ikolim
  • 15,721
  • 2
  • 19
  • 29
2
  1. Yes you can eliminate that code by creating a root_widget and returning that root widget in the build method of the app class and creating the screens in the kv file (I hope the code will explain it a litle bit better)

  2. to change the text in python of an label in the kv file you should use the kivy properties and to access the widget of another screen you use the manager.get_screen('screenname').widget command

here the main.py file

    import kivy
    kivy.require('1.10.0')
    from kivy.app import App
    from kivy.lang import Builder
    from kivy.clock import Clock
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.properties import ObjectProperty

    class MenuScreen(Screen):
        pass
    class FirstScreen(Screen):
        #create property in python
        first_screen = ObjectProperty()
        def starttimer(self):
            self.timer = Clock.schedule_once(self.screen_switch_two, 2)
        def screen_switch_two(self, dt):
            self.manager.current = 'second_screen'
    class SecondScreen(Screen):
        def starttimer(self):
            self.timer = Clock.schedule_once(self.screen_switch_three, 4)
        def screen_switch_three(self, dt):
            self.manager.current = 'third_screen'
    class ThirdScreen(Screen):
        def starttimer(self):
            self.timer = Clock.schedule_once(self.screen_switch_four, 6)
        def screen_switch_four(self, dt):
            self.manager.current = 'fourth_screen'
    class FourthScreen(Screen):
        def starttimer(self):
            self.timer = Clock.schedule_once(self.screen_switch_one, 8)
        def screen_switch_one(self, dt):
            #change the text of the label in the first_screen
            self.manager.get_screen('first_screen').first_screen.text = "Hi I'm The Fifth Screen"
            self.manager.current = 'first_screen'

    root_widget = Builder.load_string("""
    #here the screens will be created
    ScreenManager:
        MenuScreen:
        FirstScreen:
        SecondScreen:
        ThirdScreen:
        FourthScreen:
    #the menu screen is created that the method on_enter in the first screen is called and the loop through the screens can begin
    <MenuScreen>:
        Button:
            text: 'start'
            on_press: app.root.current = 'first_screen'
    <FirstScreen>:
        #link/bind the property in the kv file 
        first_screen: first_screen
        name: 'first_screen'
        on_enter: root.starttimer()
        Label:
            id: first_screen
            text: "Hi I'm The First Screen"
    <SecondScreen>:
        name: 'second_screen'
        on_enter: root.starttimer()
        Label:
            id: second_screen
            text: "Hi I'm The Second Screen"
    <ThirdScreen>:
        name: 'third_screen'
        on_enter: root.starttimer()
        Label:
            id: third_screen
            text: "Hi I'm The Third Screen"
    <FourthScreen>:
        name: 'fourth_screen'
        on_enter: root.starttimer()
        Label:
            id: fourth_screen
            text: "Hi I'm The Fourth Screen"
    """)

    class SwitchingScreenApp(App):

        def build(self):
            return root_widget

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

To structure the code better i would seperate the kv and the python code in 2 files:

main.py

import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty

class MenuScreen(Screen):
    pass
class FirstScreen(Screen):
    first_screen = ObjectProperty()
    def starttimer(self):
        self.timer = Clock.schedule_once(self.screen_switch_two, 2)
    def screen_switch_two(self, dt):
        self.manager.current = 'second_screen'
class SecondScreen(Screen):
    def starttimer(self):
        self.timer = Clock.schedule_once(self.screen_switch_three, 4)
    def screen_switch_three(self, dt):
        self.manager.current = 'third_screen'
class ThirdScreen(Screen):
    def starttimer(self):
        self.timer = Clock.schedule_once(self.screen_switch_four, 6)
    def screen_switch_four(self, dt):
        self.manager.current = 'fourth_screen'
class FourthScreen(Screen):
    def starttimer(self):
        self.timer = Clock.schedule_once(self.screen_switch_one, 8)
    def screen_switch_one(self, dt):
        self.manager.get_screen('first_screen').first_screen.text = "Hi I'm The Fifth Screen"
        self.manager.current = 'first_screen'

class SwitchingScreenApp(App):

    def build(self):
        return Builder.load_file('screen.kv')

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

and the screen.kv file

ScreenManager:
    MenuScreen:
    FirstScreen:
    SecondScreen:
    ThirdScreen:
    FourthScreen:
<MenuScreen>:
    Button:
        text: 'start'
        on_press: app.root.current = 'first_screen'
<FirstScreen>:
    first_screen: first_screen
    name: 'first_screen'
    on_enter: root.starttimer()
    Label:
        id: first_screen
        text: "Hi I'm The First Screen"
<SecondScreen>:
    name: 'second_screen'
    on_enter: root.starttimer()
    Label:
        id: second_screen
        text: "Hi I'm The Second Screen"
<ThirdScreen>:
    name: 'third_screen'
    on_enter: root.starttimer()
    Label:
        id: third_screen
        text: "Hi I'm The Third Screen"
<FourthScreen>:
    name: 'fourth_screen'
    on_enter: root.starttimer()
    Label:
        id: fourth_screen
        text: "Hi I'm The Fourth Screen"

I hope the code could help you.

joscha
  • 1,184
  • 14
  • 24
  • Thanks Yoshi. You've answered question 1 about kv Language quite nicely. :) (See the code I posted.) The approach taken in answering question 2 works for this example but ultimately I'll be taking what I seeking here and building it into a much larger application. I'd prefer placing as much interaction between screens in the build code (one place) so the code becomes easier to manage. That said I'd like to learn how to reference individual elements on each screen so I can update them. That's why in the re-posted code I'd still like to change the text on the first screen during the build. – Brad Fortner Sep 11 '17 at 08:59
1

<main.py>

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout

class screenone(Screen):
    pass
class screentwo(Screen):
    pass



class mainApp(App):
    def build(self):
        screen_manager=ScreenManager()
        screen_manager.add_widget(screenone(name='screen_one'))
        screen_manager.add_widget(screentwo(name='screen_two'))
        return screen_manager


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



<main.kv>

<screenone>:
    BoxLayout:
        Button:
            text:"go to next screen"
            on_press:root.manager.current='screen_two'
<screentwo>:
    BoxLayout:
        Button:
            text:"go back to first screen"
            on_press:root.manager.current='screen_one'
Tarun Roy
  • 11
  • 1
  • 1
    Please consider editing your answer. Don't add additional information in the comments but rather in the answer itself. From [review](https://stackoverflow.com/review/late-answers/26662667). – sanitizedUser Jul 13 '20 at 09:11
0

As is the case in all of my posts I like to post complete working code so it's immediately useful. Based on YOSHI's answer I've reworked his kv Language code so it sets-up as much of the screenmanager as possible and follows my original code. This answers question 1.

However I'd still like to know the code that will allow me to change the text on screen 1 to "Hi I'm The Fifth Screen" prior to it displayed the second time at line 55 of the code below. This will help me understand how to better navigate the bindings to update elements on individual screens.

Any help on this (with fully working code) will be greatly appreciated. Updated working code below;....brad....

import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen

class FirstScreen(Screen):
    pass

class SecondScreen(Screen):
    pass

class ThirdScreen(Screen):
    pass

class FourthScreen(Screen):
    pass

root_widget = Builder.load_string("""
ScreenManager:
    FirstScreen:
    SecondScreen:
    ThirdScreen:
    FourthScreen:
<FirstScreen>:
    first_screen: first_screen
    name: '_first_screen_'
    Label:
        id: first_screen
        text: "Hi I'm The First Screen"
<SecondScreen>:
    name: '_second_screen_'
    Label:
        id: second_screen
        text: "Hi I'm The Second Screen"
<ThirdScreen>:
    name: '_third_screen_'
    Label:
        id: third_screen
        text: "Hi I'm The Third Screen"
<FourthScreen>:
    name: '_fourth_screen_'
    Label:
        id: fourth_screen
        text: "Hi I'm The Fourth Screen"
""")

class SwitchingScreenApp(App):
    def build(self):
        Clock.schedule_once(self.screen_switch_one, 2)
        Clock.schedule_once(self.screen_switch_two, 4)
        Clock.schedule_once(self.screen_switch_three, 6)
        Clock.schedule_once(self.screen_switch_four, 8)
        # Want to place the code here that changes the first_screen text to "Hi I'm The Fifth Screen"
        Clock.schedule_once(self.screen_switch_one, 10)
        return root_widget

    def screen_switch_one(a, b):
        root_widget.current = '_first_screen_'

    def screen_switch_two(a, b):
        root_widget.current = '_second_screen_'

    def screen_switch_three(a, b):
        root_widget.current = '_third_screen_'

    def screen_switch_four(a, b):
        root_widget.current = '_fourth_screen_'

if __name__ == '__main__':
    SwitchingScreenApp().run()
Brad Fortner
  • 134
  • 1
  • 2
  • 9
0

You just have to add this line in line 55.

root_widget.get_screen('_first_screen_').first_screen.text = "Hi I'm The Fifth Screen"

first you have to bind the label (more info here) and then you can access the label text with the code above

so here is the full code:

main.py

import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen

class FirstScreen(Screen):
    pass

class SecondScreen(Screen):
    pass

class ThirdScreen(Screen):
    pass

class FourthScreen(Screen):
    pass

root_widget = Builder.load_string("""
ScreenManager:
    FirstScreen:
    SecondScreen:
    ThirdScreen:
    FourthScreen:
<FirstScreen>:
    first_screen: first_screen
    name: '_first_screen_'
    Label:
        id: first_screen
        text: "Hi I'm The First Screen"
<SecondScreen>:
    name: '_second_screen_'
    Label:
        id: second_screen
        text: "Hi I'm The Second Screen"
<ThirdScreen>:
    name: '_third_screen_'
    Label:
        id: third_screen
        text: "Hi I'm The Third Screen"
<FourthScreen>:
    name: '_fourth_screen_'
    Label:
        id: fourth_screen
        text: "Hi I'm The Fourth Screen"
""")

class SwitchingScreenApp(App):
    def build(self):
        Clock.schedule_once(self.screen_switch_one, 2)
        Clock.schedule_once(self.screen_switch_two, 4)
        Clock.schedule_once(self.screen_switch_three, 6)
        Clock.schedule_once(self.screen_switch_four, 8)
        root_widget.get_screen('_first_screen_').first_screen.text = "Hi I'm The Fifth Screen"
        Clock.schedule_once(self.screen_switch_one, 10)
        return root_widget

    def screen_switch_one(a, b):
        root_widget.current = '_first_screen_'

    def screen_switch_two(a, b):
        root_widget.current = '_second_screen_'

    def screen_switch_three(a, b):
        root_widget.current = '_third_screen_'

    def screen_switch_four(a, b):
        root_widget.current = '_fourth_screen_'

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

I hope the code could help you.

joscha
  • 1,184
  • 14
  • 24
  • The full code you posted requires a correction. :( The line 55 text requires an event dispatcher in order to run. In the case of the final code I'll post I'll use Kivy's clock. When dispatched as a clock event it works fantastic. Thanks again for your time on this. ....brad.... – Brad Fortner Sep 11 '17 at 18:25
  • could you edit the code because for me the code works fine – joscha Sep 11 '17 at 18:38
  • Yoshi - Take a good look at the screens when this version (above) runs. The first screen displayed actually says "Hi I'm The Fifth Screen". That's probably because the first screens text is updated before the actual clock calls start running. Note in the final version that I posted Kivy's clock does the text change a 9 seconds as an event and at ten seconds screen one is re-displayed with the changed text. – Brad Fortner Sep 11 '17 at 21:59
0

In order to keep to the creed of simple and complete working code with each post I've finalized the code based on the information Yoshi provided in his second code post. Hence the final and working code for the question as initially posted is as follows;

import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen

class FirstScreen(Screen):
    pass

class SecondScreen(Screen):
    pass

class ThirdScreen(Screen):
    pass

class FourthScreen(Screen):
    pass

root_widget = Builder.load_string("""
ScreenManager:
    FirstScreen:
    SecondScreen:
    ThirdScreen:
    FourthScreen:
<FirstScreen>:
    first_screen: first_screen
    name: '_first_screen_'
    Label:
        id: first_screen
        text: "Hi I'm The First Screen"
<SecondScreen>:
    name: '_second_screen_'
    Label:
        id: second_screen
        text: "Hi I'm The Second Screen"
<ThirdScreen>:
    name: '_third_screen_'
    Label:
        id: third_screen
        text: "Hi I'm The Third Screen"
<FourthScreen>:
    name: '_fourth_screen_'
    Label:
        id: fourth_screen
        text: "Hi I'm The Fourth Screen"
""")

class SwitchingScreenApp(App):
    def build(self):
        Clock.schedule_once(self.screen_switch_one, 2)
        Clock.schedule_once(self.screen_switch_two, 4)
        Clock.schedule_once(self.screen_switch_three, 6)
        Clock.schedule_once(self.screen_switch_four, 8)
        Clock.schedule_once(self.text_replace_screen_one, 9)
        Clock.schedule_once(self.screen_switch_one, 10)
        return root_widget

    def text_replace_screen_one(a,b):
        root_widget.get_screen('_first_screen_').first_screen.text = "Hi I'm The Fifth Screen"

    def screen_switch_one(a, b):
        root_widget.current = '_first_screen_'

    def screen_switch_two(a, b):
        root_widget.current = '_second_screen_'

    def screen_switch_three(a, b):
        root_widget.current = '_third_screen_'

    def screen_switch_four(a, b):
        root_widget.current = '_fourth_screen_'

if __name__ == '__main__':
    SwitchingScreenApp().run() 
Brad Fortner
  • 134
  • 1
  • 2
  • 9