Search code examples
classoopscopekivykivy-language

How to work with variables in KIVY and Python across widgets and screens


I am a beginner programmer looking to improve - I've made some pretty cool Python games with pyGame - and now found an interest in KIVY! The separation of logic and layout is great. But as a non-seasoned veteran in OOP, I am pretty much going crazy at this point. I don't know how to connect all the different parts. Most KIVY tutorials I can follow are basic and concentrate mainly on design - which is fine, but when it comes to putting real meat on the bones, there isn't much to go with.

(Then there are the ones that seemingly build GUI's for the Mars Curiosity rover - those are way over my head). I do grab bits and pieces of knowledge in the process, but my code, and my brain, ends up suffering from too many different approaches and programmer styles colliding together. [hopefully that makes some sense] I wonder if fundamentally I need to start from scratch and throw out bad advice?

In the code below I've set up a simple KIVY gui with two screens. Page one increments a variable "A", page two increments the variable "B". I am able to retrieve and display the values of both variables from each KV 'screen' because they belong to the same root screen-Manager class. But I haven't been able to figure out how to grab and manipulate the same variables in a popup window, since that isn't under the screen-Managers jurisdiction. (Somewhere in some tutorial, I learned to create a popoup in KVlang using the Factory import.) But if I want to set "A" = 0 in a popup, I am stumped. Should I use __init__, global variables, app, App.get_screen, parent/child assert, id,kv properties, bind.

Is resetting "A" & "B" to zero from the popup on either screen something easily accomplished? Am I far off base? How about changing the "B" value from a button on page one, or the "A" value from a button on page two?

test.py

from kivy.app import App
from kivy.properties import NumericProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder


class WindowManager(ScreenManager):
    pass

class Screen1(Screen):
    a = NumericProperty(0)
    varB = NumericProperty(0)

    def add_1(self):
        self.a += 1

    def min_1(self):
        self.a -= 1

class Screen2(Screen):
    b = NumericProperty(0)
    varA = NumericProperty(0)

    def add_1(self):
        self.b += 1

    def min_1(self):
        self.b -= 1

class SampleApp(App):
    def build(self):
        return kv

kv = Builder.load_file('my.kv')

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

my.kv

#: import Factory kivy.factory.Factory

WindowManager:
    Screen1:
        id: S1
        varB:S2.b
    Screen2:
        id: S2
        varA:S1.a              # THIS IS HOW I GRAB DATA FROM DIFFERENT CLASS
                               # STRUCTURES UNDER THE WINDOWMANAGER

<PopupMenu@Popup>:
    size_hint: (.25,.25)
    title: "MENU"
    Button:
        text: "RESET"
        on_release:
            print("Now What?")            # HOW DO I BRING VARIABLES INTO THIS?

<Screen1>:
    name: 'Screen_1'
    BoxLayout:
        orientation: "vertical"
        BoxLayout:
            size_hint: (1,.4)
            Button:
                size_hint: (.25,1)
                text: "A +1"
                color: 1,1,1,1
                font_size: self.width/5
                background_normal: ''
                background_color: 0,191/255,1,1
                on_release:
                    root.add_1()
            Label:
                canvas:
                    Color:
                        rgba: 1,0,1,.25
                    Rectangle:
                        pos: self.pos
                        size: self.size
                text: "A = " + str(root.a)
                underline: True
                pos_hint: {"center_x":.5,"center_y":.5}
                font_size: self.width/5
                color: 1,1,1,1
        BoxLayout:
            size_hint: (1,.4)
            Button:
                size_hint: (.25,1)
                text: "A -1"
                color: 1,1,1,1
                font_size: self.width/5
                background_normal: ''
                background_color: 0,191/255,1,1
                on_release:
                    root.min_1()     # IT'S EASY TO CALL THIS FUNCTION SINCE IT'S IN ITS ROOT.
                                     # BUT WHAT IF I WANTED TO MANIPULATE VARIABLE "B" FROM
                                     # SCREEN TWO?
            Label:
                canvas:
                    Color:
                        rgba: 1,0,1,.25
                    Rectangle:
                        pos: self.pos
                        size: self.size
                text: "B = " + str(root.varB)
                underline: True
                pos_hint: {"center_x":.5,"center_y":.5}
                font_size: self.width/5
                color: 1,1,1,1
        BoxLayout:
            size_hint: (1,.2)
            Button:
                text: "popup"
                color: 1,1,1,1
                font_size: self.width/6
                on_release:
                    Factory.PopupMenu().open()   # IS THIS THE RIGHT WAY TO MAKE A POPUP?
            Button:
                text: "Page 2"
                color: 1,1,1,1
                font_size: self.width/6
                on_release:
                    app.root.current = 'Screen_2'
                    root.manager.transition.direction = 'left'

<Screen2>:
    name: 'Screen_2'
    BoxLayout:
        orientation: "vertical"
        BoxLayout:
            size_hint: (1,.4)
            Button:
                size_hint: (.25,1)
                text: "B +1"
                color: 1,1,1,1
                font_size: self.width/5
                background_normal: ''
                background_color: 0,191/255,1,1
                on_release:
                    root.add_1()
            Label:
                canvas:
                    Color:
                        rgba: 0,0,1,.3
                    Rectangle:
                        pos: self.pos
                        size: self.size
                text: "A = " + str(root.varA)
                underline: True
                pos_hint: {"center_x":.5,"center_y":.5}
                font_size: self.width/5
                color: 1,1,1,1
        BoxLayout:
            size_hint: (1,.4)
            Button:
                size_hint: (.25,1)
                text: "B -1"
                color: 1,1,1,1
                font_size: self.width/5
                background_normal: ''
                background_color: 0,191/255,1,1
                on_release:
                    root.min_1()
            Label:
                canvas:
                    Color:
                        rgba: 0,0,1,.3
                    Rectangle:
                        pos: self.pos
                        size: self.size
                text: "B = " + str(root.b)
                underline: True
                pos_hint: {"center_x":.5,"center_y":.5}
                font_size: self.width/5
                color: 1,1,1,1
        BoxLayout:
            size_hint: (1,.2)
            Button:
                text: "popup"
                color: 1,1,1,1
                font_size: self.width/6
                on_release:
                    Factory.PopupMenu().open()
            Button:
                text: "Page 1"
                color: 1,1,1,1
                font_size: self.width/6
                on_release:
                    app.root.current = 'Screen_1'
                    root.manager.transition.direction = 'right'

Solution

  • You can use the app keyword in your <PopupMenu@Popup> rule to reference the SampleApp instance an then the ids to get to the Screens. Like this:

    <PopupMenu@Popup>:
        size_hint: (.25,.25)
        title: "MENU"
        Button:
            text: "RESET"
            on_release:
                print("Now What?", app.root.ids.S1.varB)            # HOW DO I BRING VARIABLES INTO THIS?