Adding twice the same widget raises an exception

223 views
Skip to first unread message

Damien Frikha

unread,
Aug 19, 2013, 9:47:53 AM8/19/13
to kivy-...@googlegroups.com
Hello there,

I'm trying to make my own UI widgets but I'm facing an issue while trying to add more than one of them in my root widget. I manage to get a simple example of what's not working, here :

(sorry, I'm writing this on my phone since I don't have internet currently at my workplace, so I don't have any formatting tools)


from kivy.uix.widget import Widget
from kivy.graphics import Rectangle

class MyClass(Widget):
wgt = Widget()

def __init__(self, **kwargs):
super(MyClass, self).__init__(**kwargs)
self.add_widget(self.wgt)

if __name__ == '__main__':
from kivy.app import App

class MyApp(App):
def build(self):
display = Widget()
display.add_widget(MyClass())
display.add_widget(MyClass())
return display

MyApp().run()

The app crashes because it is apparently trying to add the same "wgt" in the second instance of "MyClass" as the one from the first instance. It is a "WidgetException".

It is obviously coming from the fact that wgt is defined outside of the __init__ method, but I don't understand the logic behind this exception and how to find a workaround. I need this because I have some functions callings that acts on this widget that might be called before __init__.

Thanks in advance for your answers

Alexander Taylor

unread,
Aug 19, 2013, 11:25:35 AM8/19/13
to kivy-...@googlegroups.com
Enter code here...

When you define a variable outside the __init__ method, it is a class variable, and every instance of the class gets the same one. Here, that means every MyClass has self.wgt pointing to the same widget. Given that, it's obvious why you get the error; you try to create two MyWidget instances, but when the second one is created it tries to add the shared self.wgt which was already added by the first one.

The way to avoid this is simply to not define widgets this way - use class level variables if you explicitly want to share them for some reason. If you want individual ones per class, just define it in the __init__ method instead. All you would need to do is add a line like 'self.wgt = Widget()', then every widget creates and adds its own self.wgt object.

I'm not sure what you mean when you see you 'need this', there's almost certainly a better way to manage things. Perhaps you could give more information? It sounds like you have functions acting on the class itself (not its instances), but you probably don't need to do this.

Damien Frikha

unread,
Aug 20, 2013, 4:34:00 AM8/20/13
to kivy-...@googlegroups.com
Well then, if any variable declared outside of the __init__ method is a class variable shared by all instances of the class, how come every built-it widget defines its main attributes with this type of variable (a slider for instance has "value", "orientation", "padding"... defined as class variables) whereas they aren't exactly shared attributes since they change with instances. It came to my understanding that this variables were defined like that because their values are defined before the __init__ call, like when you define a slider by slider = Slider(value=15), the value fails to be attributed if the "value" variable is declared in __init__.

What I want do is to set an attribute of the "wgt" Widget at the creation of the MyClass instance, something like that :

class MyClass(Widget):

wgt = Widget()

wsize = NumericProperty(0)

def on_wsize(self, ins, value):
self.wgt.size = value


And the rest is the same as the previous snippet.

Message has been deleted

ZenCODE

unread,
Aug 20, 2013, 5:59:51 AM8/20/13
to kivy-...@googlegroups.com
Generally, variables declared before the init are shared by all instances.

    class Test()
        prop = NumericProperty(0)

Buy kivy automatically binds the property to each widget on instantiation, so it's then per widget. That's why you'd refer to is as "self.prop" and not "Test.prop" as the latter would refer to a single instance defined for all the classes. Is this what you are after?

    class MyClass(Widget):
        wsize = NumericProperty(0)

        def __init__(self, **kwargs):
            super(MyClass, self).__init__(**kwargs)
            if "value" in kwargs:
                self.wsize = kwargs.pop("value")

Some widgets come with their own events that you can catch easily ("on_release", "on_size"), but you can creat you worn too...


Hope that helps? Peace


Alexander Taylor

unread,
Aug 20, 2013, 7:04:09 AM8/20/13
to kivy-...@googlegroups.com
Just to be particularly explicit about ZenCODE's explanation, kivy's
property system explicitly does extra stuff above and beyond python's
natural class variable behaviour. For something like 'self.value =
NumericProperty(0)', the Property adds extra behaviour that *does* give
individual class instances their own copy of the variable, but this is
solely because kivy takes care of it. In your example you just did the
normal python behaviour with no properties, 'self.wgt = Widget()', so
you got the standard shared instance.

It's possible that you can do something like 'wgt =
ObjectProperty(Widget())' to get what you want by using kivy's property
system (but I'm not sure if that would actually copy the widget
instance...), but even if so it throws up some problems and probably
still breaks the behaviour you originally wanted.

I'm not quite sure what your example really intends, it seems like a mix
of two ideas. Do you really want to do something like:

class MyClass(Widget):
wx = NumericProperty(0)
def __init__(self, *args):
super(Myclass, self).__init__(*args)
wgt = Widget()
wgt.x = self.wx

This would create a new widget ('wgt') when MyClass is instantiated, and
assign its property 'x' (the x position) to be the wx property of
MyClass. You could then do whatever you like with the widget.

A few notes on this though, it probably still doesn't do what you really
intend:
1) The code doesn't actually do anything further with wgt (neither does
any of your example code). If you want to display it, you probably want
to do 'self.add_widget(wgt)' after creating it.
2) In your last post, 'self.wgt.size = value' will fail because size is
actually a list (or maybe a tuple, whatever) or two values for the width
and height respectively. That's why I replaced it with the example
property 'x', which is a single number controlling the x position of the
widget.
3) Using kv language makes much of the creation and binding of widgets
very easy, rather than mucking around in python, though of course it's
worth understanding both ways. For instance, if you want every instance
of MyClass to have a child widget, you could easily write that in kv
instead of fiddling with __init__.


On 20/08/13 09:38, Damien Frikha wrote:
> Well then, if any variable declared outside of the __init__ method is a class variable shared by all instances of the class, how come every built-it widget defines its main attributes with this type of variable (a slider for instance has "value", "orientation", "padding"... defined as class variables) whereas they aren't exactly shared attributes since they change with instances. It came to my understanding that this variables were defined like that because their values are defined before the __init__ call, like when you define a slider by slider = Slider(value=15), the value fails to be attributed if the "value" variable is declared in __init__.
>
> What I want do is to set an attribute of the "wgt" Widget at the creation of the MyClass instance, something like that :
>
> class MyClass(Widget):
>
> wgt = Widget()
>
Reply all
Reply to author
Forward
0 new messages