I want to pass a variable to the class of a custom widget to construct it.
Python:
class Customwidget(BoxLayout)
mystring = StringProperty()
def __init__(self, **kwargs):
super(Customwidget, self).__init__(**kwargs)
print(mystring)
Kivy
Root:
BoxLayout:
orientation: 'vertical'
Customwidget:
mystring: 'stringcontents'
When this is executed, mystring remains empty. How do I pass it to the class such that it can be used within init for logic in defining the widget's contents?
Problem - empty string
When this is executed, mystring remains empty. How do I pass it to the
class such that it can be used within init for logic in defining the
widget's contents?
Root Cause
The load_kv() function is called from run(), therefore,
any widget whose styling is defined in this kv file and is created
before run() is called (e.g. in __init__), won’t have its styling
applied. Note that build() is called after load_kv has been
called.
Solution
Use Kivy Clock schedule_once() method to invoke the print() function after Kivy completed styling.
Snippets
def __init__(self, **kwargs):
super(Customwidget, self).__init__(**kwargs)
Clock.schedule_once(lambda dt: print(f"mystring={self.mystring}"), 0.02)
Example
main.py
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.clock import Clock
class Customwidget(BoxLayout):
mystring = StringProperty()
def __init__(self, **kwargs):
super(Customwidget, self).__init__(**kwargs)
Clock.schedule_once(lambda dt: print(f"mystring={self.mystring}"), 0.02)
runTouchApp(Builder.load_string("""
Screen:
BoxLayout:
orientation: 'vertical'
Customwidget:
mystring: 'stringcontents'
"""))
Related
i am working with kivy and i wrote this code.
from kivy.app import App
from kivy.uix.button import Button
from kivy.graphics import Rectangle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
class Main(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
def on_press(self):
sub()
class sub(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class Rect(App):
def build(self):
return Main()
if __name__=='__main__':
Rect().run()
.kv file
<Main>:
FloatLayout:
canvas:
Rectangle:
pos:100,0
size:200,200
Button:
text:'Click'
on_press:root.on_press()
<Sub>:
FloatLayout:
canvas:
Rectangle:
pos:100,0
size:400,400
when i run the code,the window showing rectangle and button opens up. when i click on button, i expect to show a bigger rectangle but nothing actually happens.what should i do to dislay the bigger rectangle which is defined in another class by clicking 'click'.Thanks in advance.
First of all, the code you put here contains some errors, you are instantiating an object called main() instead of the object created Main() with capitalized M, in Rect class.
About what you are wanting, when the action on_press() is called, you are instantiating an object of type sub(), but you are not placing it inside the desired widget, that is, the object is actually being created, but not appears on the screen.
To solve this you need to add this newly created object to your main widget, in that case just call self.add_widget()
The code with the fixes are:
RectApp.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.graphics import Rectangle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
class Main(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
def on_press(self):
self.add_widget(Sub())
class Sub(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class RectApp(App):
def build(self):
return Main()
if __name__=='__main__':
RectApp().run()
rect.kv
<Main>:
FloatLayout:
canvas:
Rectangle:
pos: 100, 0
size: 200, 200
Button:
text: 'click'
on_press: root.on_press()
<Sub>:
FloatLayout:
canvas:
Rectangle:
size: 400, 400
pos: 300, 0
I have dynamically created textinput widgets in a .kv file using kv language. However i would like to set the text in main python file using a for loop by referencing their ids.
I have written the following code:
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
class setText(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
texts=['txtip1','txtip2']
IDS=self.ids.keys()
for i in range(IDS):
self.ids[IDS[i]].text=texts[i]
class DocApp(App):
def build(self):
return Builder.load_file("docapp\Doc.kv")
return setText()
if __name__=="__main__":
DocApp().run()
Doc.kv
# File: docapp.py
#: import TextInput kivy.uix.textinput.TextInput
GridLayout:
cols:1
on_kv_post:
[self.add_widget(TextInput(id=str(i))) for i in range(2)]
I have searched through other questions but couldn't find suitable solution. Please help
The problem in your code, is that id only works in the kv syntax.
So you need to reference your widgets some other way.
In case you need to do it that way, dynamically adding in kv and accessing in a loop in python, you could try something like the example below.
The text will change after 5 seconds in this case. If you know that the only children of this gridlayout will be the widgets you want to access, you might as well just iterate the children.
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
KV = """
#: import TextInput kivy.uix.textinput.TextInput
GridLayout:
cols:1
on_kv_post:
for i in range(2): app.dynamic_widgets.append(TextInput())
for wid in app.dynamic_widgets: self.add_widget(wid)
"""
class DocApp(App):
dynamic_widgets = []
def build(self):
Clock.schedule_once(self.set_text, 5)
return Builder.load_string(KV)
def set_text(self, dt):
texts=['txtip1','txtip2']
for text, wid in zip(texts, self.dynamic_widgets):
wid.text = text
if __name__=="__main__":
DocApp().run()
Now if you remove one of the widgets, you probably want to remove it from the list also.
You can create the TextInput widgets dynamically in kv by creating the kv dynamically. Here is a modification of your code that does that:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
class DocApp(App):
def build(self):
# schedule the call to add the texts
Clock.schedule_once(self.setText)
# return the root widget from the kvString
return layoutRoot
def setText(self, dt):
texts=['txtip1','txtip2']
IDS=self.root.ids.keys()
i = 0
for id in IDS:
self.root.ids[id].text=texts[i]
i += 1
if __name__=="__main__":
# Create the kv string here
kvString = '''
GridLayout:
cols:1
'''
for i in range(2):
kvString += ' TextInput:\n' + ' id: ' + str(i) + '\n'
# Load the kv string
layoutRoot = Builder.load_string(kvString)
# and finally, run the App
DocApp().run()
I have eliminated the setText class (assuming it was only a holder for the setText method), and placed that method in the App. The Clock.schedule_once() schedules the call to setText to happen after the App has been created.
I have written a python code in python file(main.py) to create a kivy app that contains dynamically created labels which works fine.
Here is main.py file
main.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
class createLabels(GridLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.cols=1
labels=[Label(text='Label '+str(i)) for i in range(5)]
[self.add_widget(label) for label in labels]
class DocApp(App):
def build(self):
return createLabels()
if __name__=="__main__":
DocApp().run()
However i would like to dynamically create labels similar to the one above using a kivy language file (.kv). I am not sure whether we can use lists and for statements in .kv file. I tried the solution mentioned in one of the similar type question but it didn't work.
You could use on_kv_post which runs when the kv class which you use on_kv_post is ready. And dynamically, like in push of buttons, is basically the same as in the example below.
from kivy.app import App
from kivy.lang import Builder
KV = '''
#: import Label kivy.uix.label.Label
GridLayout:
cols: 1
on_kv_post:
[self.add_widget(Label(text="Label " + str(i))) for i in range(9)]
'''
class TestApp(App):
def build(self):
return Builder.load_string(KV)
if __name__ == '__main__':
TestApp().run()
I wanted to make a dynamic class of a group of widget so whenever I add them to my main app, I had to only bring in the changes from somewhere(e.g. a python file, class etc, here I've done them in explicit lists), but binding the widget with properties like "on_text" which respond to events didn't really work, other properties like 'text', 'hint_text' worked perfectly but on_text doesn't really budge. I can't figure out the cause for this because I've checked the correct object being passed along the functions, below is my code:
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.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
print(cur_properties[attr])
curwidget.bind(**(cur_properties[attr]))
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'on_text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
And, in the kivy file:
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: 'testlable'
TextInput:
id: mytext
hint_text: 'some test'
What is the cause? And how can I fix it?
There are 2 ways to make a binding of a property:
*.py
object.bind(property=callback)
*.kv
object:
on_property: callback
So in the case of making the connection in python you should not use on_text, but text.
To verify the change, the Label can not be edited from the GUI, so I will use the TextInput to write on the Label:
*.py
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.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
curwidget.bind(**(cur_properties[attr]))
print(cur_properties[attr])
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
*.kv
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: mytext.text # <----
TextInput:
id: mytext
hint_text: 'some test'
I want to create a method that can directly access the user-defined properties of a Kivy Widget Class as a list or dict. (In order to pass them in as an argument into another instance of the same class).
python file:
from kivy.uix.behavior import ButtonBehavior
from kivy.uix.image import Image
from kivy.properties import StringProperty
class ExampleWidget(ButtonBehavior, Image):
name = StringProperty()
hometown = StringProperty()
school = StringProperty()
def clone_self(self):
# Psuedo-Code
args = [v for v in self.user_defined_properties]
clone = ExampleWidget(*args)
return clone
I have tried a number of things, such as dir, var and dict as well using the .get() value as defined in the kivy properties docs all to no avail.
My current set up has two methods, one that returns a manually defined dict of properties, the other which creates and returns the clone using the first method.
Using this, my code is functional however I'm quite sure that a lot of it is unnecessary due to some built in method I'm not entirely sure how to access.
Thank you for reading.
You declare your properties on class level. See this code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import StringProperty, NumericProperty, Property
Builder.load_string('''
<MyWidget>:
Button:
text: 'Print properties'
on_press: root.print_properties()
''')
class MyWidget(BoxLayout):
name = StringProperty()
hometown = StringProperty()
school = StringProperty()
num = NumericProperty()
def print_properties(self):
for k, v in vars(self.__class__).items():
if isinstance(v, Property):
print(k)
class TestApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
TestApp().run()
Compare it with
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import StringProperty, NumericProperty, Property
Builder.load_string('''
<MyWidget>:
Button:
text: 'Print properties'
on_press: root.print_properties()
''')
class MyWidget(BoxLayout):
def __init__(self, *args):
super(BoxLayout, self).__init__(*args)
self.name = StringProperty()
self.hometown = StringProperty()
self.school = StringProperty()
self.num = NumericProperty()
def print_properties(self):
for k, v in vars(self).items():
if isinstance(v, Property):
print(k)
class TestApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
TestApp().run()
EDIT: As I've just discovered, there's also properties() method that should be used instead of vars():
for k, v in self.properties().items():
print(type(v))