21

I'm having trouble changing the color of a simple widget in Kivy. I can set the color when I create the widget, but I can't change it afterwards.

Here is the simple layout definition file circletest.kv. It defines a circle where the color (actually just the r, from rgba), position and size are all linked to variables in the widget class.

#:kivy 1.4.1

<CircleWidget>:
    canvas:
        Color:
            rgba: self.r,1,1,1
        Ellipse:
            pos: self.pos
            size: self.size

Here's the application circletest.py. It creates and displays the simple widget. The color and position are successfully set when the object is created. When the widget is clicked the widget can change it's own position, but when I try to change the color nothing happens.

import kivy
kivy.require('1.4.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget

Builder.load_file('circletest.kv')

class CircleWidget(Widget):

    def __init__(s, **kwargs):
        s.size= [50,50]
        s.pos = [100,50]
        s.r = 0
        super(CircleWidget, s).__init__(**kwargs)

    def on_touch_down(s, touch):
        if s.collide_point(touch.x,touch.y):    
            s.pos = [s.pos[1],s.pos[0]]           # This works
            s.r = 1.0                       # <---- This does nothing!

class TestApp(App):

    def build(s):
        parent = Widget()
        w = CircleWidget()
        parent.add_widget(w)
        return parent

if __name__ == '__main__':
    TestApp().run()

Can anyone see the problem?

UPDATE

Still not sure what the answer to this question is, but I do have a work around:

In the .kv file I pointed the color to a variable in my object. Works for extracting the initial color:

Color:
    rgba: self.col

When I want to change the color from the .py file I loop through all the instructions in the canvas and modify the first one of type "Color". Obviously this is a hack, and won't work on widgets with more than one Color: property:

for i in s.canvas.get_group(None):
    if type(i) is Color:
        i.r, i.g, i.b, i.a = v
        break

I wrapped that all up in a property so it's neat to use:

class CircleWidget(Widget):

    def get_col(s):
        return s._col

    def set_col(s,v):
        for i in s.canvas.get_group(None):
            if type(i) is Color:
                i.r, i.g, i.b, i.a = v
                break
        s._col = v

    col = property(get_col, set_col)

    def __init__(s, **kwargs):
        s.size= [50,50]
        s.pos = [100,50]
        s._col = (1,1,0,1)
        super(CircleWidget, s).__init__(**kwargs)

    def on_touch_down(s, touch):
        if s.collide_point(touch.x,touch.y):    
            s.col = (s.col[::-1]) # Set to some other color

Seems to work for now. Please let me know if you know a better way of doing this. I'm sure there must be a simpler way, and that I'm missing something obvious!

Andy
  • 255
  • 1
  • 2
  • 8
  • Could the problem be in the float you are trying to set? – Difusio Oct 21 '12 at 11:56
  • Hi @Difusio. Are you suggesting there might be a type conflict here? I'm pretty sure r should be a float. I just tried setting `s.r=0.0001` in the constructor, and I get similar behaviour. I tried setting r to a list in the constructor `s.r=[0.1]` and got an error. However when I do this elswhere in the class it does not cause an error, suggesting that `s.r` is not accessed by the framework after the object has been created. Perhaps there is some call I can make to force the framework to update the values and redraw the widget? – Andy Oct 21 '12 at 12:27
  • I've done some more experiments with the type of my color variable. In the .kv file I pointed the rgba value to a single variable `rgba: self.c` and then initialised it as a list in the constructor `self.c = [1,1,1,1]`. This exhibits exactly the same behaviour: it sets the color on creation, but does not allow me to set it afterwards. I also tried using kivy's `Color` class: `s.c = kivy.graphics.Color(1,1,1,1)`, but that gave me a type error because it does not support iteration. – Andy Oct 21 '12 at 12:46

2 Answers2

18

The answer by tshirtman is correct, here is the explanation of what's going on.

In your kv file when you set

<CircleWidget>:
    canvas:
        Color:
            rgba: self.r, 1, 1, 1
        Ellipse:
            pos: self.pos
            size: self.size

The line rgba: self.r, 1, 1, 1 tries to update the value of rgba whenever there is a change to the value of r. This is done implicitly in kv language by binding, which can be done on a kivy Property as it implements a Observer Pattern.

The variable r in your code was updated but it's just a variable that doesn't provide any indication that it's value has changed and can not be bound to. If you notice your changes to pos work because pos is a ReferenceListProperty.

General rule for programming in Kivy, if you want to change code depending on a property of a Widget/Object use a Kivy Property. It provides you the option to Observe Property changes and adjust your code accordingly either explicitly through bind/on_property_name events or implicitly through kv language as mentioned above.

qua-non
  • 4,152
  • 1
  • 21
  • 25
16

In your initial version, you were just missing the declaration of the property

from kivy.properties import NumericProperty

in the header and

r = NumericProperty(0)

just under class CircleWidget(Widget):

also, you state that your kv file is named circletest.kv, but your app is named TestApp, so you should change one of them to make them coherent, or your kv file won't be found, but as you don't report any issue with that, i guess it's only a typo in the question. edit: just saw the Builder.load_file ok,

cheers.

Tshirtman
  • 5,859
  • 1
  • 19
  • 26
  • this works when the ellipse is draw with the `Builder.load_file` and `Builder.load_string` but it fails when the vertex instructions are added dynamically (i.e., with `with self.canvas:`). I created another question [here](http://stackoverflow.com/questions/17058098/how-do-i-update-the-color-of-an-dynamically-added-without-using-the-builder). Thanks! – toto_tico Jun 16 '13 at 04:42
  • I added a bounty... I am in need of this answer since my current approach doesn't seem appropriate. I would really appreciate if you can help me with it. Currently, I solved it removing and creating an new instance of the vertex instruction each time. I don't understand why does it has to be necessarily a **new instance** so I asked another [question here](http://stackoverflow.com/questions/17130388/why-do-i-need-to-create-a-new-instance-of-a-line-instead-of-simply-update-or-add). – toto_tico Jun 16 '13 at 05:16
  • For me this works for setting an initial colour, but then it doesn't work when I try to change the colour under `on_touch_down()` – Tooniis Sep 11 '19 at 12:26