Reputation: 13
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:
KivyFolder
modules(folder)
screens(folder)
screen1.kv
screen2.kv
main.py
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:
KivyFolder
modules(folder)
screens(folder)
Intro.kv
(and other screens as separate .kv files
main.py
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
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