3

What would be a good way to add widgets to a screen from python code (dynamicly)?

My goal is to have two 'main' screens. The content of the second screen depends on what has been selected on the Main screen. This content can be seen for simplicity as a variable number of buttons (all content like number of buttons, background images etc stored in the database and updated independently). So the logic I imagine as follows: when MainScreen button pressed, I go to database and retrieve the data by key 'button N' and show it on the second screen in the form of customized Button/Label widgets . I also want to get back to the MainScreen to select another button in it if needed.

Lets assume I have two screens which static graphics is defined in KV file:

<MainScreen>
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "SELECT BUTTON"
        Button:
            on_press: root.show_buttons(self)
            text: "Button 1"
        Button:
            on_press: root.show_buttons(self)
            text: "Button 2"

<AnotherScreen>:
    BoxLayout:
        orientation: 'vertical'
        Label:
            text: "Select BUTTON"
        Button: 
            on_press: root.manager.current = 'Main'
            text: "BACK"

MainScreen is fully static. When a button is pressed, I need to invoke AnotherScreen and add content to it depending on which button is pressed. I made an attempt to do it as follows (and failed):

class MainScreen(Screen):
    def show_buttons(self, btn):
        if btn.text == 'Button 1':
            another_screen.add_widget(Label(text="Button 1 pressed"))
            sm.current = "Another"
            return sm

class AnotherScreen(Screen):
    pass

class CoolApp(App):
    def build(self): 
        global sm
        sm = ScreenManager()
        main_screen = MainScreen(name='Main')
        sm.add_widget(main_screen)
        global another_screen
        another_screen = AnotherScreen(name='Another')
        sm.add_widget(another_screen)
        return sm


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

In this case, dynamic widget another_screen.add_widget(Label(text="Button pressed")) is added to a screen which by defintion can have only one child (already defined in KV). I didn't get exception but I got two different widgets rendered at the same time which is equally bad.

I guess I need to add dynamic widgets to BoxLayout in because it is the root widget of that screen. However, I don't know if it is possible to reference widget from different class and if so, is it that appropriate?

Thank you!

Agenobarb
  • 143
  • 2
  • 10

1 Answers1

1

One way is using ids.

from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label

class MainScreen(Screen):
    first_btn_press = True
    def show_buttons(self, btn):
        if btn.text == 'Button 1':
            print(self.manager.ids)
            if self.first_btn_press:
                self.manager.ids.another.ids.box.add_widget(Label(text="Button 1 pressed"))
                self.first_btn_press = False
            self.manager.current = "another"

class AnotherScreen(Screen):
    pass

kv_str = Builder.load_string("""
ScreenManager:
    MainScreen:
        id: main
    AnotherScreen:
        id: another


<MainScreen>
    name: "main"
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "SELECT BUTTON"
        Button:
            on_press: root.show_buttons(self)
            text: "Button 1"
        Button:
            on_press: root.show_buttons(self)
            text: "Button 2"

<AnotherScreen>:
    id: another
    name: "another"
    BoxLayout:
        id: box
        orientation: 'vertical'
        Label:
            text: "Select BUTTON"
        Button: 
            on_press: root.manager.current = 'main'
            text: "BACK"


""")



class CoolApp(App):
    def build(self):
        return kv_str


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

In the end it is up to you. Another possibility would be using ObjectProperties in the ScreenManager or directly in the app class. The solution above is my preferred way of doing it.

PalimPalim
  • 2,892
  • 1
  • 18
  • 40
  • Thanks! I was also trying this approach, however the problem is that if I invoke second screen multiple times it will result to addition of multiple same buttons into it. That's why I have button "Back": I want to return to the MainScreen if needed. If I after return I again press buttin "Button 1" I will have two labels "Button 1 pressed". – Agenobarb Aug 14 '17 at 14:14
  • If this is not your desired behaviour you could simply add a Boolean which you change once the button has been pressed together with an if clause, so next time it only switches the screen. I can add that maybe tomorrow. – PalimPalim Aug 14 '17 at 14:30
  • Please also add more details to your question. So it is easier to understand what your goal is. – PalimPalim Aug 14 '17 at 14:32
  • I have added some details to the post, not sure if it clarifies things a bit? Looking forward to your 'Boolean' solution, thank you for your help. – Agenobarb Aug 14 '17 at 14:55
  • I mean you could just as easily create and setup all the widgets.Then set the opacity of the widgets to 0 (false) which is "invisible" or 1 (true) which is "visible". All of what you're attempting to do could be done in both kv and pure Python. In kv you could just add opacity: to the end of each rule – suroh Aug 15 '17 at 00:00
  • Sorry for delay! Yes, it is working great - simple and elegant, thank you! – Agenobarb Aug 18 '17 at 10:08