Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Altering a kivy property from another thread

I'm experimenting with some kivy code. I tried modifing a kivy property(text_colour) from a tread created with threading lib. Program works fine but the thread does not change the property.

I also tried to create a method in class about which gets the value as an argument, which also failed.

from kivy.app import App

from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty


import threading
import random
import time


def zaaa():
    import time
    time.sleep(3)
    ScatterTextWidget.text_colour = [0, 0, 1, 1]
    print "function ran"   

t = threading.Thread(target= zaaa)
t.start()


class ScatterTextWidget(BoxLayout):

    text_colour = ListProperty([1, 0, 0, 1])

    def change_label_colour(self, *args):
        colour = [random.random() for i in xrange(3)] + [1]
        self.text_colour = colour

    def press(self, *args):
        self.text_colour = [1, 1, 1, 1]


class TataApp(App):
    def build(self):
        return ScatterTextWidget()


if __name__ == "__main__":
    TataApp().run()

OUTPUT:

[INFO              ] Kivy v1.8.0
[INFO              ] [Logger      ] Record log in /home/mbp/.kivy/logs/kivy_14-02-26_44.txt
[INFO              ] [Factory     ] 157 symbols loaded
[DEBUG             ] [Cache       ] register <kv.lang> with limit=None, timeout=Nones
[DEBUG             ] [Cache       ] register <kv.image> with limit=None, timeout=60s
[DEBUG             ] [Cache       ] register <kv.atlas> with limit=None, timeout=Nones
[INFO              ] [Image       ] Providers: img_tex, img_dds, img_pygame, img_pil, img_gif 
[DEBUG             ] [Cache       ] register <kv.texture> with limit=1000, timeout=60s
[DEBUG             ] [Cache       ] register <kv.shader> with limit=1000, timeout=3600s
[DEBUG             ] [App         ] Loading kv </home/mbp/workspace/KiviPlay/tata.kv>
[DEBUG             ] [Window      ] Ignored <egl_rpi> (import error)
[INFO              ] [Window      ] Provider: pygame(['window_egl_rpi'] ignored)
libpng warning: iCCP: known incorrect sRGB profile
[DEBUG             ] [Window      ] Display driver x11
[DEBUG             ] [Window      ] Actual window size: 800x600
[DEBUG             ] [Window      ] Actual color bits r8 g8 b8 a8
[DEBUG             ] [Window      ] Actual depth bits: 24
[DEBUG             ] [Window      ] Actual stencil bits: 8
[DEBUG             ] [Window      ] Actual multisampling samples: 2
[INFO              ] [GL          ] OpenGL version <4.3.12618 Compatibility Profile Context 13.251>
[INFO              ] [GL          ] OpenGL vendor <ATI Technologies Inc.>
[INFO              ] [GL          ] OpenGL renderer <AMD Radeon HD 7700 Series>
[INFO              ] [GL          ] OpenGL parsed version: 4, 3
[INFO              ] [GL          ] Shading version <4.30>
[INFO              ] [GL          ] Texture max size <16384>
[INFO              ] [GL          ] Texture max units <32>
[DEBUG             ] [Shader      ] Fragment compiled successfully
[DEBUG             ] [Shader      ] Vertex compiled successfully
[DEBUG             ] [ImagePygame ] Load </usr/lib/python2.7/site-packages/Kivy-1.8.0-py2.7-linux-x86_64.egg/kivy/data/glsl/default.png>
[INFO              ] [Window      ] virtual keyboard not allowed, single mode, not docked
[INFO              ] [Text        ] Provider: pygame
[DEBUG             ] [Cache       ] register <kv.loader> with limit=500, timeout=60s
[INFO              ] [Loader      ] using a thread pool of 2 workers
[DEBUG             ] [Cache       ] register <textinput.label> with limit=None, timeout=60.0s
[DEBUG             ] [Cache       ] register <textinput.width> with limit=None, timeout=60.0s
[DEBUG             ] [Atlas       ] Load </usr/lib/python2.7/site-packages/Kivy-1.8.0-py2.7-linux-x86_64.egg/kivy/data/../data/images/defaulttheme.atlas>
[DEBUG             ] [Atlas       ] Need to load 1 images
[DEBUG             ] [Atlas       ] Load </usr/lib/python2.7/site-packages/Kivy-1.8.0-py2.7-linux-x86_64.egg/kivy/data/../data/images/defaulttheme-0.png>
[DEBUG             ] [ImagePygame ] Load </usr/lib/python2.7/site-packages/Kivy-1.8.0-py2.7-linux-x86_64.egg/kivy/data/../data/images/defaulttheme-0.png>
[INFO              ] [GL          ] NPOT texture support is available
[INFO              ] [OSC         ] using <multiprocessing> for socket
[DEBUG             ] [Base        ] Create provider from mouse
[DEBUG             ] [Base        ] Create provider from probesysfs
[DEBUG             ] [ProbeSysfs  ] using probsysfs!
[INFO              ] [Base        ] Start application main loop
<kivy.properties.ListProperty object at 0x124f870>
function ran
[INFO              ] [Base        ] Leaving application in progress...
like image 611
Kartopukus Avatar asked Feb 26 '14 03:02

Kartopukus


2 Answers

You cannot modify a kivy property or do any OpenGL related work from an external thread.

The solution is to schedule callbacks with kivy's Clock that will call a function from kivy's main thread and do the work for you.

Personally, when I use a second thread, I use a queue for the inter-thread comm as follows:

from Queue import Queue

 class KivyQueue(Queue):
    '''
    A Multithread safe class that calls a callback whenever an item is added
    to the queue. Instead of having to poll or wait, you could wait to get
    notified of additions.

    >>> def callabck():
    ...     print('Added')
    >>> q = KivyQueue(notify_func=callabck)
    >>> q.put('test', 55)
    Added
    >>> q.get()
    ('test', 55)

    :param notify_func: The function to call when adding to the queue
    '''

    notify_func = None

    def __init__(self, notify_func, **kwargs):
        Queue.__init__(self, **kwargs)
        self.notify_func = notify_func

    def put(self, key, val):
        '''
        Adds a (key, value) tuple to the queue and calls the callback function.
        '''
        Queue.put(self, (key, val), False)
        self.notify_func()

    def get(self):
        '''
        Returns the next items in the queue, if non-empty, otherwise a
        :py:attr:`Queue.Empty` exception is raised.
        '''
        return Queue.get(self, False)

The second threads uses put to put things on the queue. And the callback, when called, schedules a kivy callback using a trigger. The function that the kivy's main thread then calls calls the get function of the queue and sets the appropriate property.

That is helpful if you need to set many properties. If you just need to set a single property, what I do is from the second thread I schedule a callabck that uses partial to make the value part of the function. E.g:

# this may only be called from the main kivy thread
def set_property(value, *largs):
    self.kivy_property = value

# the second thread does this when it wants to set self.kivy_property to 10
Clock.schedule_once(partial(set_property, 10))
like image 77
Matt Avatar answered Sep 23 '22 04:09

Matt


It works for me:

from kivy.app import App
from kivy.uix.label import Label
import threading

import time

class ScatterTextWidget(Label):

    def __init__(self,**kwargs):
    self.text = 'nima'
    self.color = [0, 1, 1, 1]
    super(ScatterTextWidget, self).__init__(**kwargs)
a = ScatterTextWidget()
def zaaa():
    import time
    time.sleep(3)
    a.color = [0, 0, 1, 1]
    print "function ran"   

t = threading.Thread(target= zaaa)
t.start()

class TataApp(App):
    def build(self):
        return a


if __name__ == "__main__":
    TataApp().run()
like image 39
nim4n Avatar answered Sep 23 '22 04:09

nim4n