I'm working on a simple Kivy Popup and I'm confused as to why I can't reference my class variable, 'self.text_1', from within the constructor of the Popup class, but I can do so with the variable 'self.title'.
First, I'll start with the ubiquitous, "I'm new to Python and Kivy, so perhaps I'm just missing something obvious, but I'm at a loss."
I've tried to find answers, but nothing so far has seemed to cover this topic, at least in a way that I can understand and draw the connection. Below is my problem and some simplified code for demonstration.
From within the kv file > Label widget > text, I can refer to 'text_1' from the CustomPopup class in my py file by using 'root.text_1' when the variable is placed outside of the constructor (either before or after), but I cannot do so when placed within. The 'self.title' variable is setup the exact same way as 'self.text_1', but I CAN get that value without issue.
If I un-comment this line, using 'root.text_1' in the kv file returns the correct value.
class CustomPopup( Popup ):
# I can reference this text from the kv file using "root.text_1"
# text_1 = 'blah blah blah'
If instead I try to use 'self.text_1' from within the constructor, I get an Attribute error. However, I have no issues using the variable 'self.title' just below 'self.text_1'.
AttributeError: 'CustomPopup' object has no attribute 'text_1'
def __init__( self, foo = 'bar' ):
super().__init__()
self.foo = foo
# I can't reference this text from the kv file using "root.text_1". Why?
self.text_1 = 'blah blah {foo}'.format( foo = self.foo )
# I can reference this text from the kv file using "root.title"
self.title = 'Title {foo}!'.format( foo = self.foo )
What is the difference here as to why I can get the value from one constructor variable, but not another with ostensibly similar syntax?
I'm using Python 3.7.1 (conda version : 4.5.12) and Kivy 1.10.1.
Python:
import kivy
from kivy import Config
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.popup import Popup
kivy.require( '1.10.1' )
Config.set( 'graphics', 'fullscreen', 0 )
Config.set( 'graphics', 'height', 600 )
Config.set( 'graphics', 'width', 800 )
Config.set( 'graphics', 'resizable', 0 )
Config.set( 'kivy', 'exit_on_escape', 0 )
class CustomPopup( Popup ):
# I can reference this text from the kv file using "root.text_1"
# text_1 = 'blah blah blah'
def __init__( self, foo = 'bar' ):
super().__init__()
self.foo = foo
# I can't reference this text from the kv file using "root.title". Why?
self.text_1 = 'blah blah {foo}'.format( foo = self.foo )
# I can reference this text from the kv file using "root.title"
self.title = 'Title {foo}!'.format( foo = self.foo )
class CustomPopupTestApp( App ):
def build( self ):
return CustomPopup()
#Instantiate top-level/root widget and run it
if __name__ == "__main__":
CustomPopupTestApp().run()
kv:
<CustomPopup>:
id: popup
title: root.title
title_size: 30
title_align: 'center'
size_hint: 0.8, 0.8
auto_dismiss: False
pos_hint: { 'x' : 0.1 , 'y' : 0.1 }
GridLayout:
cols: 1
rows: 2
spacing: 15
padding: 15
Label:
id: content
text: root.text_1
font_size: 25
padding: 15, 25
size_hint_y: None
text_size: self.width, None
height: self.texture_size[ 1 ]
halign: 'center'
GridLayout:
cols: 2
rows: 1
AnchorLayout:
anchor_x : 'center'
anchor_y : 'bottom'
padding: 20
Button:
id: yes
text: 'Yes'
font_size: 30
size_hint: 0.8, 0.4
AnchorLayout:
anchor_x : 'center'
anchor_y : 'bottom'
padding: 20
Button:
id: no
text: 'No'
font_size: 30
size_hint: 0.8, 0.4
I would like to setup the text for my pop-up and use the str.format() method to insert a variable text element so that my message is tailored to the circumstances at hand, rather than generic or hard-coded with multiple options.
In this case, I pass an argument of 'foo' to the constructor, set the variable 'self.foo' equal to 'foo', then refer to 'self.foo' within my 'self.title' string and 'self.text_1' string.
If I place 'text_1' outside of the constructor, while I can retrieve the text value, since 'foo' hasn't been defined within that scope, I can't reference it to format my string.
I am interested in other solutions and any opportunity to learn, but ultimately, if there's a way to make this work without a workaround, that would be ideal.
Thanks in advance for any help. I've already learned a ton from everyone on this site.
P.S. - If I've done something "stupid" or offended your sensibilities, please provide a suggestion or correction rather than just berate me. Sometimes people get negative and it's (usually) not necessary.
Through research and testing, I've found my answers.
It turns out, firstly, that I wasn't calling 'self.title' like I thought I was. Therefore, I wasn't seeing different behavior for 'self.title' vs 'self.text_1'; I was seeing the same behavior. So, how then was I able to get my title to show, but not my content?
The Popup widget has inherent attributes of 'title' and 'content'. When I defined 'self.title' in CustomPopup(), I just provided that attribute's value. Even after removing the corresponding kv code 'title: root.title', the same value defined for 'self.title' was still displayed. This was the crucial moment the clued me in that I was distracted by the red herring of 'self.title' vs 'self.text_1'.
Once I ruled out the issue that I'm seeing different behavior for two otherwise identical lines of code, I looked deeper into how I was defining my CustomPopup class. That's when I came across this post that demonstrated how to handle this properly: Kivy: How to get class Variables in Popup.
Long story... slightly less long... I updated my super method to inherit my 'text_1' StringProperty so that I could reference it from the CustomPopup object!
Here's the updated working solution:
Python:
import kivy
from kivy import Config
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
kivy.require( '1.10.1' )
Config.set( 'graphics', 'fullscreen', 0 )
Config.set( 'graphics', 'height', 600 )
Config.set( 'graphics', 'width', 800 )
Config.set( 'graphics', 'resizable', 0 )
Config.set( 'kivy', 'exit_on_escape', 0 )
class CustomPopup( Popup ):
text_1 = StringProperty( '' )
def __init__( self, foo = 'bar' ):
super( CustomPopup, self ).__init__()
self.foo = foo
self.text_1 = 'blah blah {foo}'.format( foo = self.foo )
self.title = 'Title {foo}!'.format( foo = self.foo )
class CustomPopupTestApp( App ):
def build( self ):
blarg = CustomPopup()
return blarg
#Instantiate top-level/root widget and run it
if __name__ == "__main__":
CustomPopupTestApp().run()
kv:
<CustomPopup>:
id: popup
# title: root.title
title_size: 30
title_align: 'center'
size_hint: 0.8, 0.8
auto_dismiss: False
pos_hint: { 'x' : 0.1 , 'y' : 0.1 }
GridLayout:
cols: 1
rows: 2
spacing: 15
padding: 15
Label:
id: content
text: root.text_1
font_size: 25
padding: 15, 25
size_hint_y: None
text_size: self.width, None
height: self.texture_size[ 1 ]
halign: 'center'
GridLayout:
cols: 2
rows: 1
AnchorLayout:
anchor_x : 'center'
anchor_y : 'bottom'
padding: 20
Button:
id: yes
text: 'Yes'
font_size: 30
size_hint: 0.8, 0.4
AnchorLayout:
anchor_x : 'center'
anchor_y : 'bottom'
padding: 20
Button:
id: no
text: 'No'
font_size: 30
size_hint: 0.8, 0.4
Notice that the kv file no longer refers to 'root.title', yet the title is still displayed properly.
The final product pictured in the link below is a Kivy popup with the structure/formatting defined in the kv file with the functionality and variable text defined in Python. It looks much cleaner to me than doing it all on the Python side.
CustomPopupTest Working Solution
I hope this helps someone else like so many other posts have helped me.