Reputation: 1549
I'm reading "Kivy – Interactive Applications and Games in Python Second Edition" while writing and testing the source code in the book.
When I was finishing chapter 3 I got to this error:
Exception ignored in: 'kivy.properties.observable_list_dispatch'
Traceback (most recent call last):
File "kivy/properties.pyx", line 579, in kivy.properties.Property.dispatch (/tmp/pip-install-rsswmpdy/kivy/kivy/properties.c:7216)
File "kivy/_event.pyx", line 1214, in kivy._event.EventObservers.dispatch (/tmp/pip-install-rsswmpdy/kivy/kivy/_event.c:14036)
File "kivy/_event.pyx", line 1120, in kivy._event.EventObservers._dispatch (/tmp/pip-install-rsswmpdy/kivy/kivy/_event.c:13194)
File "/home/madtyn/PycharmProjects/learning_kivy/comics/drawingspace.py", line 8, in on_children
self.status_bar.counter = len(self.children)
AttributeError: 'DrawingSpace' object has no attribute 'status_bar'
I tried to locate the error by comparing my code with the downloaded source code from the book, but I found no relevant difference. In both versions which are almost identical, I don't appreciate any difference in the relation between status_bar and DrawingSpace.
I'm pasting my code below. I'm omitting these files:
toolbox.*
comicwidgets.*
generaloptions.*
because I think they are not relevant, so everything it's easier. But I will paste them on demand if anyone asks or this doesn't get resolved.
comiccreator.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.anchorlayout import AnchorLayout
kivy.require('1.9.0')
Builder.load_file('toolbox.kv')
Builder.load_file('drawingspace.kv')
Builder.load_file('comicwidgets.kv')
Builder.load_file('generaloptions.kv')
Builder.load_file('statusbar.kv')
class ComicCreator(AnchorLayout):
pass
class ComicCreatorApp(App):
def build(self):
return ComicCreator()
if __name__ == '__main__':
ComicCreatorApp().run()
comiccreator.kv
# File name: comiccreator.kv
#:kivy 1.9.0
<ComicCreator>:
AnchorLayout:
anchor_x: 'left'
anchor_y: 'top'
ToolBox:
id: _tool_box
drawing_space: _drawing_space
comic_creator: root
size_hint: None, None
width: 100
AnchorLayout:
anchor_x: 'right'
anchor_y: 'top'
DrawingSpace:
id: _drawing_space
status_bar: _status_bar # <====== Here we define the status_bar property!!!
general_options: _general_options
tool_box: _tool_box
size_hint: None, None
width: root.width - _tool_box.width
height: root.height - _general_options.height - _status_bar.height
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
BoxLayout:
orientation: 'vertical'
GeneralOptions:
id: _general_options
drawing_space: _drawing_space
comic_creator: root
size_hint: 1,None
height: 48
StatusBar:
id: _status_bar
size_hint: 1,None
height: 24
drawingspace.py
# File name: drawingspace.py
from kivy.properties import ObjectProperty
from kivy.uix.relativelayout import RelativeLayout
class DrawingSpace(RelativeLayout):
def on_children(self, instance, value):
self.status_bar.counter = len(self.children) # Here the error states that
# status_bar attr does not exist
drawingspace.kv
# File name: drawingspace.kv
#:kivy 1.9.0
#:import drawingspace drawingspace
<DrawingSpace@RelativeLayout>:
canvas.before:
Line:
rectangle: 0, 0, self.width - 4, self.height - 4
StickMan:
statusbar.py
# File name: statusbar.py
import kivy
kivy.require('1.9.0')
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ObjectProperty
class StatusBar(BoxLayout):
counter = NumericProperty(0)
previous_counter = 0
def on_counter(self, instance, value):
if value == 0:
self.msg_label.text = "Drawing space cleared"
elif value - 1 == self.__class__.previous_counter:
self.msg_label.text = "Widget added"
elif value + 1 == StatusBar.previous_counter:
self.msg_label.text = "Widget removed"
self.__class__.previous_counter = value
statusbar.kv
# File name: statusbar.kv
#:kivy 1.9.0
#:import statusbar statusbar
<StatusBar@BoxLayout>:
msg_label: _msg_label
orientation: 'horizontal'
Label:
text: 'Total Figures: ' + str(root.counter)
Label:
id: _msg_label
text: "Kivy started"
Upvotes: 1
Views: 513
Reputation: 123
I am following the same book and had the same issue! After reading these responses, @JohnAnderson is correct - DrawingSpace.on_children()
gets called before status_bar
exists within the DrawingSpace
. But why?
Remember that the root widget of the app here is a ComicCreator
as defined in comiccreator.kv
. If you take a look at the instance of DrawingSpace
there, status_bar
is defined right after its own id
, so it would seem that there shouldn't be a problem.
But, we must remember that kv class rules are executed first. So looking at drawingspace.kv
, we see that the <DrawingSpace>
rule imports the python class (so it already knows about the on_children
method) and then adds a StickMan
within the rule. All that happens BEFORE the INSTANCE of DrawingSpace
gets a status_bar
attribute added to it within the comiccreator.kv
file.
Simply delete the StickMan
from drawingspace.kv
and the error will disappear. [Edit the following is incorrect: If you want, you can add StickMan
as a child of DrawingSpace
in comiccreator.kv
(after status_bar
has been added) and you will visually get the same result with no error.]
Lastly, you should remove @RelativeLayout
from <DrawingSpace@RelativeLayout>
. Once you define a custom class in python and inherit from a base class, you no longer need to inherit from a base class in kv using the @
operator. See the notes on page 10 for more info on that.
Upvotes: 1
Reputation: 38822
I suspect what is happening is that your on_children()
method of DrawingSpace
is being called before the status_bar
property is set. Since the on_children()
method is called whenever the children
of DrawingSpace
changes, you can protect the reference to status_bar
by adding a check of whether it is set:
class DrawingSpace(RelativeLayout):
def on_children(self, instance, value):
if self.status_bar is not None:
self.status_bar.counter = len(self.children)
As to why your code needs this and the code in your book doesn't - I cannot guess, since I don't have that book.
Upvotes: 2
Reputation: 29450
Defining properties in kv can introduce parse order issues when things depend on them, as you've found. The best solution is probably to only use dynamically created properties for simple things, and otherwise just define the properties normally in the class definition.
Upvotes: 1