Use On_Press Event to Change Screen without KV Language for Dynamically Created Buttons

Question:

How do you use an On-Press event to change screen for dynamically created buttons in python, and without using KV language?

Goal:

Be able to navigate to new screen from clicking on dynamically created button,

[without needing to create button in Kivy, and still getting to use Screenmanager in both Python and Kivy (not sure if you have to stick with either Python or Kivy throughout entire program?]

Things I've already tried:

  1. Using button_share.bind(on_press = self.changer), then this:

def changer(self,*args):
    ScreenManager()
    screenmanager.current = 'MainScreen'

But I get the error ScreenManagerException: No Screen with name "MainScreen".

Suspicion:

I think this is because I'm creating a new instance of ScreenManager, instead of referencing the existing one. To combat this issue, I considered instantiating Screenmanager() in the App class, then referencing that instantiation in the my button def changer(self, *args) method, but that's useless if it's not the same ScreenManager I'm actually using for all my screens. And those are all defined in KV language. I wouldn't be able to switch them all over without a substantial amount of effort.

  1. Using:

button_share.bind(on_press=partial(app.sm.setter('current'), (app.sm, "MainScreen")))`

But the error I get here is ValueError: ScreenManager.current accept only str

Below is a fully runnable example:

Note: In this example, I want to click 'Continue Editing' button, then click on 'Test 1', 'Test 2' or 'Test 3' button and have it take me to another screen.

Python Code:

from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.uix.widget import Widget
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from functools import partial

class ScrollableLabelDataEntryInstructions(BoxLayout):
    pass

class NewGarageScreen(Screen):
    pass

class ContinueEditingScreen(Screen):
    pass

class GarageNameBoxLayout(BoxLayout):
    box_share2 = ObjectProperty()
    sm = ScreenManager()

    def __init__(self, **kwargs):
        super(GarageNameBoxLayout, self).__init__(**kwargs)
        self.orientation = "vertical"
        Clock.schedule_interval(self.create_button, 5)

    def create_button(self, *args):
        self.box_share2.clear_widgets()
        app = App.get_running_app()
        #put GarageNameStartList data into app class, then pull from it in this class
        top_button_share = 1.1
        color = (.4, .4, .4, 1)
        for i in range(len(app.GarageNameStartList)):
            top_button_share -= .4
            id_ = app.GarageNameStartList[i]

            button_share = Button(background_normal='',
                                  background_color = color,
                                  id = id_,
                                  pos_hint = {"x": 0, "top": top_button_share},
                                  size_hint_y = None,
                                  height = 60,
                                  font_size = 30,
                                  text = app.GarageNameStartList[i])
            button_share.bind(on_press = self.changer)
            #button_share.bind(on_press=partial(app.sm.setter('current'), (app.sm, "MainScreen")))
            self.box_share2.add_widget(button_share)

    def changer(self,*args):
        ScreenManager()
        #app = App.get_running_app()
        screenmanager.current = 'MainScreen'

class BackHomeWidget(Widget):
    pass

class MainScreen(Screen):
    pass

class AnotherScreen(Screen):
    pass

class ScreenManagement(ScreenManager):
    pass

presentation = Builder.load_file("example_on_press.kv")

class MainApp(App):
    GarageNameStartList = ["Test1", "Test2", "Test3"]

    def Update_GarageNameStartList(self, *args):
        self.GarageNameStartList = ["Test1", "Test2", "Test3"]   


    def build(self):
        return presentation

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

KV Code:

#: import FadeTransition kivy.uix.screenmanager.FadeTransition

ScreenManagement:
    transition: FadeTransition()
    MainScreen:
    AnotherScreen:
    NewGarageScreen:
    ContinueEditingScreen:

<SmallNavButton@Button>:    
    font_size: 32
    size: 125, 50    
    color: 0,1,0,1

<MedButton@Button>:
    font_size: 30
    size_hint: 0.25, 0.1
    color: 0,1,0,1

<BackHomeWidget>:
    SmallNavButton:
        on_release: app.root.current = "main"
        text: "Home"
        pos: root.x, root.top - self.height

<MainScreen>:
    name: "main"
    FloatLayout: 
        MedButton:
            on_release: app.root.current = "edit"
            text: "Edit"
            pos_hint: {"x":0.3728, "top": 0.4}

<AnotherScreen>:
    name: "edit"
    BackHomeWidget:
        SmallNavButton:
            on_release: app.root.current = "main"
            text: "Back"
            pos: root.x, root.top - (2.25*(self.height))
    FloatLayout:
        MedButton:
            on_release: app.root.current = "continueediting"
            text: "Continue Editing"
            pos_hint: {"x":0.25, "top": 0.6} 
        MedButton:
            on_release: app.root.current = "newgarage"
            text: "Create New"
            pos_hint: {"x":0.3728, "top": 0.4}

<NewGarageScreen>:
    name: "newgarage"
    BackHomeWidget:
        SmallNavButton:
            on_release: app.root.current = "edit"
            text: "Back"
            pos: root.x, root.top - (2.25*(self.height))
    FloatLayout:
        MedButton:
            text: "1. Groundfloor"
            pos_hint: {"x":0, "top": 0.6}


<GarageNameBoxLayout>:
    box_share2: box_share2
    ScrollView:
        GridLayout:
            id: box_share2
            cols: 1
            size_hint_y: None
            size_hint_x: 0.5
            spacing: 5
            padding: 130
            height: self.minimum_height
            canvas:
                Color: 
                    rgb: 0, 0, 0
                Rectangle:
                    pos: self.pos
                    size: self.size         

<ContinueEditingScreen>:
    name: "continueediting"
    GarageNameBoxLayout:
    BackHomeWidget:
        SmallNavButton:
            on_release: app.root.current = "edit"
            text: "Back"
            pos: root.x, root.top - (2.25*(self.height))

1 answer

  • answered 2018-03-13 21:26 eyllanesc

    Your code can be improved in the following things:

    • You do not have to create box_share2 in the .py since you're creating it in the .kv

    • When you use sm = ScreenManager() you are creating another ScreenManager different from the original, that is not necessary.

    • It is not necessary to use range and len, make your code less readable, you just have to iterate.

    • If we look at the structure of the .kv we see that the presentation object is the ScreenManager so you can get it via app.root.

    Using the above your code the solution is:

    [...]
    
    class GarageNameBoxLayout(BoxLayout):
        def __init__(self, **kwargs):
            super(GarageNameBoxLayout, self).__init__(**kwargs)
            self.orientation = "vertical"
            Clock.schedule_interval(self.create_button, 5)
    
        def create_button(self, *args):
            self.box_share2.clear_widgets()
            app = App.get_running_app()
            sm = app.root
    
            #put GarageNameStartList data into app class, then pull from it in this class
            top_button_share = 1.1
            color = (.4, .4, .4, 1)
            for text in app.GarageNameStartList:
                top_button_share -= .4
                id_ = text
                button_share = Button(background_normal='',
                                      background_color = color,
                                      id = id_,
                                      pos_hint = {"x": 0, "top": top_button_share},
                                      size_hint_y = None,
                                      height = 60,
                                      font_size = 30,
                                      text = text)
                button_share.bind(on_press=lambda *args: setattr(sm, 'current', "main"))
                self.box_share2.add_widget(button_share)
    
    [...]