I am trying to initialize some values of widgets in Kivy on program start, with no success. Simplified Python code is:
inital_text = "init text"
class MainApp(App):
def initialize_widgets(self):
self.root.ids.my_label.text = initial_text
if __name__ == '__main__':
MainApp().run()
MainApp.initialize_widgets(App)
And the relevant piece from kv file is:
Label:
id: my_label
text: "default text"
When I run the program, the label says "default text". When quitting it, I get the error:
line 5, in initialize_widgets
self.root.ids.my_label.text = initial_text
AttributeError: type object 'App' has no attribute 'root'
I have tried other workarounds, some quite desperate ones, with no success.
Use on_start event to initialize the label's text on program start. In the example below, a Clock.schedule_once event was added to show the initial value and then the change.
Note
In this example, the root widget is Label widget (root) and the root's dictionary (root.ids) is empty. If there is another widget with id: my_textinput, added as children of the root then root's dictionary will contain one id i.e. my_textinput. A print(self.root.ids) will demonstrate this.
self - The keyword self references the “current widget instance” i.e. App.
Kivy Language » ids
When the kv file is processed, weakrefs to all the widgets tagged with ids are added to the root widget’s ids dictionary.
Snippets
from kivy.app import App
initial_text = "init text"
class MainApp(App):
def on_start(self):
self.root.text = initial_text
if __name__ == '__main__':
MainApp().run()
Example
main.py
from kivy.app import App
from kivy.clock import Clock
initial_text = "init text"
class MainApp(App):
def on_start(self):
Clock.schedule_once(self.initialize_widgets, 5)
def initialize_widgets(self, dt):
self.root.text = initial_text
if __name__ == '__main__':
MainApp().run()
Output
Related
In a part of an app I am developing, I would like to able the user view the logger that python & kivy output to see any sort of warnings or issues that may have been caused.
I searched this and there way only one stackoverflow issue regarding this: so issue on redirecting python logging onto a kivy label
I tried the exact same code (the one checked as an answer) but all I got was a label that said WOO % and a timer as its last character.
I had an idea which was to access the log record that kivy writes to but practically it won't work because for how long should I access the file? Should I create a while True: read_file() loop and put it in another thread and run it forever? Ultimately, it was not a good ides.
If this is my code:
from kivy.lang import Builder
from kivy.app import App
class MainApp(App):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.kv = Builder.load_string('''
#:kivy 2.0.0
TextInput:
# redirect (preferably copy) logger text here
id: logger_text
readonly: True
''')
def build(self):
return self.kv
if __name__ == '__main__':
MainApp().run()
How can I redirect (but preferably copy) this logger onto my text input?
You can use the concepts from the so question that you cited. Here is a modified version of that so answer employing your kivy code:
import logging
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.app import App
from kivy.clock import Clock
class MyLabelHandler(logging.Handler):
def __init__(self, label, level=logging.NOTSET):
super(MyLabelHandler, self).__init__(level=level)
self.label = label
def emit(self, record):
"using the Clock module for thread safety with kivy's main loop"
def f(dt=None):
self.label.text += self.format(record) + '\n' #"use += to append..."
Clock.schedule_once(f)
class MainApp(App):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.kv = Builder.load_string('''
FloatLayout: # added because `id` on root object has no effect
TextInput:
# redirect (preferably copy) logger text here
id: logger_text
readonly: True
''')
def build(self):
# use Kivy Logger
Logger.level = logging.INFO
Logger.addHandler(MyLabelHandler(self.kv.ids.logger_text, logging.INFO))
# send some logging from this app
Clock.schedule_interval(self.send_log, 2)
return self.kv
def send_log(self, dt):
# This is just to demonstrate some logging fom this App
Logger.info('MyApp: this is a test at ' + str(dt) + ' seconds')
logging.info('this is a python log')
if __name__ == '__main__':
MainApp().run()
In order to understand the logic behind Kivy kv language, I'm trying to rewrite a minimal application by replacing the automatic load of a kv file by a call to Builder.load_string().
Here's my starting point (source: examples 1-2, 1-3): two files, weather.py and weather.kv :
weather.py:
from kivy.app import App
class WeatherApp(App):
pass
if __name__ == '__main__':
WeatherApp().run()
and weather.kv:
Label:
text: "Hello World"
Up to there, everything's alright
.
But if I try to load manually the kv stuff, I just get a black screen (and no error message). My code:
from kivy.app import App
from kivy.lang import Builder
KV = '''
Label
text: "Hello World"
'''
Builder.load_string(KV)
class WeatherApp(App):
pass
if __name__ == '__main__':
WeatherApp().run()
I'm obviously missing something here, but what ? Any help would be appreciated !
When you create a .kv there are basic but strict rules, among them there can only be one toplevel, the toplevel is identified because it does not have "<>", besides for the App to recognize it it must have the same name of the application in lowercase, in your case the .kv is called weather.kv and the WeatherApp app. But the above does not happen if you use Builder, in the case that the .kv has a root as it is in your case Builder.load_string() returns it so you must return it in the build method of the App:
from kivy.app import App
from kivy.lang import Builder
KV = '''
Label:
text: "Hello World"
'''
root = Builder.load_string(KV)
class WeatherApp(App):
def build(self):
return root
if __name__ == '__main__':
WeatherApp().run()
I want to make a simple program that is just showing definitions that are stored in text file.One label and button to show next definition. I try to do it with documentation but i cannot find how to load text into label. Can someone show me to some good resources or code samples ?
My code for now (i want to build in on top of example from kivy website):
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
return Label(text = 'Hello world')
if __name__ == '__main__':
MyApp().run()
The easiest way to update widgets in the UI are by binding to their properties. This can be done in code, but the real power of kivy in my opinion comes from using it's declarative UI language. Using kv, you get automatic binding.
Here is a quick example of what you might do:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
kv = '''
BoxLayout:
orientation: 'vertical'
Label:
text: app.text
Button:
text: 'click me'
on_press: app.clicked()
'''
class MyApp(App):
text = StringProperty("hello world")
def build(self):
return Builder.load_string(kv)
def clicked(self):
self.text = "clicked!"
if __name__ == '__main__':
MyApp().run()
In the kv description of the UI, you tell kivy that you want the text on the Label to be bound to a StringProperty on the app which you defined on the class. The auto-binding means that anytime you set a value to that property (like in the clicked function), the UI will update with the new value automatically.
Let's say I define on the fly in Kivy a few widgets (buttons) and dynamically assign their id.
I'm not using kv language in this use case.
I can keep a reference of a widget id without keeping track of the widget itself : then I'd like to access the widget through its id.
Can I do something like "get widget by id" ?
(If I had defined the widget in a kv file, I could have used self.ids.the_widget_id to access the widget itself through its id)
Kivy widgets make tree structure. Children of any widget are avaiable through children atribute. If you want, you can keep reference only to root window and then iterate over it's widgets using walk method:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text="...", id="1")
button.bind(on_release=self.print_label)
l1 = BoxLayout(id="2")
l2 = BoxLayout(id="3")
self.add_widget(l1)
l1.add_widget(l2)
l2.add_widget(button)
def print_label(self, *args):
for widget in self.walk():
print("{} -> {}".format(widget, widget.id))
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
walk() and walk_reverse() method were added to kivy.uix.widget.Widget in 1.8.1 version of Kivy. For older versions you need to recursively parse tree yourself:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text="...", id="1")
button.bind(on_release=self.print_label)
l1 = BoxLayout(id="2")
l2 = BoxLayout(id="3")
self.add_widget(l1)
l1.add_widget(l2)
l2.add_widget(button)
def print_label(self, *args):
children = self.children[:]
while children:
child = children.pop()
print("{} -> {}".format(child, child.id))
children.extend(child.children)
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
You can retrieve the widget using directly the ids. For example in your code you can modify the Button text with the following snippet:
self.ids.2.ids.3.ids.1.text = '!!!!'
You can change properties of each widget using ids:
self.ids['order_number'].text='S1212'
Trying to follow this guide : https://kivy.org/docs/guide/lang.html#accessing-widgets-defined-inside-kv-lang-in-your-python-code
I am attempting to access a widget using an id definition. This works well inside the root widget but it doesn't seem to work outside of it.
As an example here's a bare minimum code representing my issue :
GUI.kv file :
<PlotBox#BoxLayout>:
graph2:graph2_id
BoxLayout:
id:graph2_id
<RootWidget#BoxLayout>:
graph:graph_id
BoxLayout:
id:graph_id
PlotBox:
python file :
#kivy imports
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class PlotBox(BoxLayout):
graph2 = ObjectProperty(None)
def __init__(self,**kwargs):
super(PlotBox,self).__init__(**kwargs)
self.graph2.add_widget(Button(text="This doesn't work"))
class RootWidget(BoxLayout):
graph = ObjectProperty(None)
def __init__(self,**kwargs):
super(RootWidget,self).__init__(**kwargs)
self.graph.add_widget(Button(text='This works'))
class GUIApp(App):
def build(self):
self.root = RootWidget()
return self.root
if __name__ == "__main__":
GUIApp().run()
I get the error :
AttributeError: 'NoneType' object has no attribute 'add_widget'
On the RootWidget, it works even if I don't use graph = ObjectProperty(None).
On my other widget, it's like the id doesn't get created.
According to the docs:
The # character is used to separate your class name from the classes you want to subclass. [...]
From what is concluded that it is an equivalent way to do inheritance in the .kv similar to python so you should only select one of those ways. That causes PlotBox from .py to never be invoked.
Another error, according to the docs, I do not know if it's your error but the .kv must be gui.kv, with lowercase.
children are not loaded directly after executing the parent's constructor so adding it in the constructor can generate problems, a recommendation and a very common practice in kivy is to use Clock.
All the above I have implemented in the following codes:
gui.kv
<PlotBox>:
graph2:graph2_id
BoxLayout:
id:graph2_id
<RootWidget>:
graph:graph_id
BoxLayout:
id:graph_id
PlotBox:
main.py
#kivy imports
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.clock import Clock
class PlotBox(BoxLayout):
graph2 = ObjectProperty(None)
def __init__(self,**kwargs):
super(PlotBox,self).__init__(**kwargs)
Clock.schedule_once(lambda dt: self.graph2.add_widget(Button(text="This now works")))
class RootWidget(BoxLayout):
graph = ObjectProperty(None)
def __init__(self,**kwargs):
super(RootWidget,self).__init__(**kwargs)
self.graph.add_widget(Button(text='This works'))
class GUIApp(App):
def build(self):
root = RootWidget()
return root
if __name__ == "__main__":
GUIApp().run()
Output:
I think self.graph2 just hasn't been set yet during the __init__ - the __init__ has to return before any children can be added.
You can work around this by doing something like Clock.schedule_once(function_that_adds_the_button, 0).
I'm working under the assumption that you want this code to run when the app is being created, no later.
kv.
<PlotBox>:
BoxLayout:
id:graph2_id
<RootWidget>:
BoxLayout:
id:graph_id
PlotBox:
id: plot
py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class PlotBox(BoxLayout):
pass
class RootWidget(BoxLayout):
pass
class GUIApp(App):
def build(self):
root = RootWidget()
# We can add things to the Root during build before we return it
# This means we add this information before the user sees anything
root.ids.graph_id.add_widget(Button(text='This works'))
# See end of answer for more about this
root.ids.plot.ids.graph2_id.add_widget(Button(text='This works!'))
return root
if __name__ == "__main__":
GUIApp().run()
Firstly, you don't need Object Properties to access ids, you can do it through ids or children:
self.ids.IDGOESHERE
OR
self.children[INDEXOFIDGOESHERE]
As for this line:
root.ids.plot.ids.graph2_id.add_widget(Button(text='This works!'))
Root has an instance of the plotbox class with the id 'plot'. The Plot class (and therefore all instances of the plot class) have an instance of the BoxLayout with the id graph that we can access.
So what we're doing is:
Root -> Plot -> Graph2
If we were to add another plotbox with the id 'big_plot', then we could do either what we did before to get one Graph2 or we could get a different graph2 because it belongs to a different instance of the plotbox.
What we did before
Root -> Plot -> Graph2
A different id, therefore a different widget.
Root -> big_plot -> Graph2
Unless you're invoking super, you'll rarely need to use the init method in a Kivy Widget Class (or so in my experience anyway).
Edit:
If you're going to access super long addresses repeatedly, you can wrap them in a function to get them.
example:
Not great:
def func_one(self):
newtext = 'new'
self.ids.IDONE.ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
def func_two(self):
newtext = 'newtwo'
self.ids.IDONE.ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
def func_three(self):
newtext = 'newthree'
self.ids.IDSONE.Ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
Better:
def long_address(self):
return self.ids.IDSONE.ids.IDSTWO.ids.IDTHREE.ids.IDFOUR
def func_one(self):
newtext = 'new'
self.long_address().text = newtext
def func_two(self):
newtext = 'newtwo'
self.long_address().text = newtext
def func_three(self):
newtext = 'newthree'
self.long_address().text = newtext