EndPuppet
EndPuppet

Reputation: 13

How to use variables in kivy, that are defined in another .py module (not main.py)?

I want to have all the variables for an app defined in a .py module separate from the main.py file. Then I want to use those variables inside a kivy file.

I have tried the code below with several variations and there are always errors. The module works fine for defining for example different Fonts but, when it comes to properties(ObjectProperty) it does not work.

Project structure:

Code:

main.py

from kivy.app import App

from kivy.uix.screenmanager import ScreenManager, Screen

from os import listdir
from kivy.lang import Builder

kv_screens = './screens/'
for kv in listdir(kv_screens):
    Builder.load_file(kv_screens+kv)

class Screen1(Screen):
    pass

class Screen2(Screen):
    pass

class ScreenManagement(ScreenManager):
    pass

presentation = Builder.load_file('main.kv')

class MainApp(App):

    import modules.variables

    def build(self):
        return presentation

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

main.kv

#:kivy 1.10.1

ScreenManagement:
    Screen1:
    Screen2:

screen1.kv

#:kivy 1.10.1

<Screen1>:
    name: 's1'
    BoxLayout:
        orientation: 'vertical'
        Label:
            text: 'Screen 1'
            font_size: app.font_size_one
        Button:
            text: 'Go To Screen 2'
            on_press:
                root.manager.current = 's2'

screen2.kv

#:kivy 1.10.1

<Screen2>:
    name: 's2'
    BoxLayout:
        orientation: 'vertical'
        Label:
            text: 'Screen 2'
            font_size: '20dp'
        Button:
            text: 'Go To Screen 1'
            on_press:
                root.manager.current = 's1'

variables.py

from kivy.properties import ObjectProperty

font_size_one = ObjectProperty('40dp')
font_size_two = ObjectProperty('60dp')

Now doing it like this produces this error:

AttributeError: 'NoneType' object has no attribute 'bind'

I would expect to be able to call a variable since it is imported at MainApp(App), so 'app.variable name should read it.

I have also tried:

font_size: app.variables.font_size_one

and:

font_size: app.modules.variables.font_size_one

Then I tried importing the module before everything else with the same result.

I also tried putting it into a class and calling that class in .kv like this:

variables.py

from kivy.properties import ObjectProperty


class FVArs():
    font_size_one = ObjectProperty('40dp')
    font_size_two = ObjectProperty('60dp')

screen1.kv

#:kivy 1.10.1

<Screen2>:
    name: 's2'
    BoxLayout:
        orientation: 'vertical'
        Label:
            text: 'Screen 2'
            font_size: FVars().font_size_two
        Button:
            text: 'Go To Screen 1'
            on_press:
                root.manager.current = 's1'

Error:

NameError: name 'FVars' is not defined

Thank you for your answers.

Bye

EDIT: Since the answer below works on the example code but not in my actual project I am adding the relevant project code:

Project structure:

main.py

from kivy.app import App

from modules.global_variables import globalVariables

from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.label import Label

from kivy.properties import StringProperty, NumericProperty, ObjectProperty, Property

from kivy.core.window import Window          

background_color = (0.1, 0.1, 0.1, 1)        
Window.clearcolor = background_color         

from os import listdir                       
from kivy.lang import Builder   

kv_screens = './screens/'                       
for kv in listdir(kv_screens):                  
    Builder.load_file(kv_screens+kv)            
kv_elements = './elements/'                      
for kv in listdir(kv_elements):                  
    Builder.load_file(kv_elements+kv)       

class IntroScreen(Screen): 
    user_name = StringProperty("Troll")   

class ScreenManagement(ScreenManager):
    pass        

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

class MainApp(App):

    title_size = globalVariables.title_size

    def build(self):                         
        self.title = 'Kivy Omnia!'           
        return presentation                   


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

global_variables.py

from kivy.properties import ObjectProperty

class globalVariables():

    title_size = ObjectProperty('101sp')

main.kv

#:kivy 1.10.1

ScreenManagement:
    IntroScreen:

intro.kv

#:kivy 1.10.1


<IntroScreen>:
    name: "intro"
    BoxLayout:
        orientation: 'vertical'
        Label:
            text: "Hello " +  root.user_name + "!"
            font_size: app.title_size

Error:

kivy.lang.builder.BuilderException: Parser: File "C:\Users\Ghost\CodeProjects\phyton\kivyomnia\screens\Intro.kv", line 10: ... 8: Label: 9: text: "Hello " + root.user_name + "!"

10: font_size: app.title_size 11: font_name: "Exo-B" 12: AnchorLayout: ... BuilderException: Parser: File "C:\Users\Ghost\CodeProjects\phyton\kivyomnia\screens\Intro.kv", line 10: ... 8: Label: 9: text: "Hello " + root.user_name + "!" 10: font_size: app.title_size 11: font_name: "Exo-B" 12: AnchorLayout: ... AttributeError: 'NoneType' object has no attribute 'bind' File "C:\Python37-32\lib\site-packages\kivy\lang\builder.py", line 249, in create_handler return eval(value, idmap), bound_list File "C:\Users\Ghost\CodeProjects\phyton\kivyomnia\screens\Intro.kv", line 10, in font_size: app.title_size File "C:\Python37-32\lib\site-packages\kivy\lang\parser.py", line 75, in getattribute object.getattribute(self, '_ensure_app')() File "C:\Python37-32\lib\site-packages\kivy\lang\parser.py", line 70, in _ensure_app app.bind(on_stop=lambda instance:

File "C:\Python37-32\lib\site-packages\kivy\lang\builder.py", line 615, in _apply_rule rctx['ids']) File "C:\Python37-32\lib\site-packages\kivy\lang\builder.py", line 254, in create_handler cause=tb)

So if just reposition the:

title_size = globalVariables.title_size

... inside the IntroScreen(Screen) and then inside Intro.kv:

font_size: root.title_size

... it works. But the point is to have all the global variables available across all screens.

Another question:

if app.something gets a what is inside main app class and root.something gets what is in the root class of that file then how do you call something from another class that is not app or root. If that makes sense.

Upvotes: 0

Views: 1099

Answers (1)

Jerome Escalante
Jerome Escalante

Reputation: 4387

You can import your FVars class with this line:

from modules.variables import FVars

Then inside your MainApp class, you can create a class variable with the data of FVars's class variable.

class MainApp(App):
    f1 = FVars.font_size_one

You can then access it on your kv files:

font_size: app.f1

Here is some basic example:

main.py

from kivy.app import App
from kivy.lang import Builder

from modules.variables import FVars

class Test(App):
    f1 = FVars.font_size_one

    def build(self):
        return Builder.load_file("test.kv")

Test().run()

test.kv

BoxLayout:
    Label:
        text: "Hello"
        font_size: app.f1

modules/variables.py

from kivy.properties import ObjectProperty

class FVars():
    font_size_one = ObjectProperty('40dp')
    font_size_two = ObjectProperty('60dp')

UPDATE

To prevent the error, you can load the kv files inside the build method.

def build(self):                         
    self.title = 'Kivy Omnia!'           

    kv_screens = './screens/'                       
    for kv in listdir(kv_screens):                  
        Builder.load_file(kv_screens+kv)            

    kv_elements = './elements/'                      
    for kv in listdir(kv_elements):                  
        Builder.load_file(kv_elements+kv)  

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

    return presentation

There's a plenty of ways on how to access a variable inside a class which is neither an app nor root, you can just set the id property of that widget.

Here's a basic example:

main.py

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout

class SecondClass(BoxLayout):
    someNumber = 123 # Let's say we wanted to print the value of this variable when we press the button.

class FirstClass(BoxLayout):
    pass

class TestApp(App):

    def build(self):
        return FirstClass()


TestApp().run()

test.kv

<SecondClass>:

<FirstClass>:

    Button: 
        text: "Print the Numbah"
        on_press: print(app.root.ids.secondclass.someNumber) # We can do something like this
    SecondClass:
        id: secondclass

We can also do something like this:

<SecondClass>:

<FirstClass>:

    Button: 
        text: "Print the Numbah"
        on_press: print(root.ids.secondclass.someNumber) 
    SecondClass:
        id: secondclass

Or this:

<SecondClass>:

<FirstClass>:

    Button: 
        text: "Print the Numbah"
        on_press: print(secondclass.someNumber)
    SecondClass:
        id: secondclass

Or even this:

<SecondClass>:

<FirstClass>:
    second: secondclass
    Button:
        text: "Print the Numbah"
        on_press: print(app.root.second.someNumber)

    SecondClass:
        id: secondclass

And this:

<SecondClass>:

<FirstClass>:
    second: secondclass
    Button:
        text: "Print the Numbah"
        on_press: print(root.second.someNumber)
    SecondClass:
        id: secondclass

Upvotes: 1

Related Questions