Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Kivy: Properly start a background process that updates GUI elements

I have a Python script that performs some intensive processing of user's files and can take some time. I've build a user interface to it using Kivy, that allows the user to select the file, processing mode and shows them some messages as the process goes on.

My problem is that when the main Kivy loop passes calls the underlying user interface, the window freezes.

From what I've understood, the proper way of resolving this is to create a separate process to which the script would be off-loaded and from which it would send the updates to the user interface.

However, I was not able to find an example of how to do this or any specification on how to send messages from a separate thread back into application.

Could someone please give an example of how to do this properly or point me to the documentation pertaining to the subject?

Update:

For the sake of keeping the program maintainable I would like to avoid calling the elements of loops of processor from the main thread and instead call one long process that comes back to updated elements of the GUI, such as the progress bar or a text field. It looks like those elements can be modified only from the main kivy thread. How do I gain access to them from the outside?

like image 525
chiffa Avatar asked Oct 10 '14 15:10

chiffa


People also ask

How do I get kivy app to run in the background?

Kivy runs on android using the python-for-android project, which support android services. This works in a simple way, you basically bundle two main.py in your application, one for the UI, one for the service. The UI can start the services on start.

Can you use tkinter and kivy together?

It probably is possible to run Kivy in an opengl context within a tkinter window, but there's no support for this and you'd have to write a fair amount of code to get it to work. Essentially you'd need to write a tkinter backend provider for the Kivy window.

How to use Kivy in Python to build desktop apps?

Kivy tutorial – Build desktop GUI apps using Python. 1 Installation. If you have multiple versions of Python installed on your computer, then you will have to install Kivy in the version that you wish to ... 2 Kivy GUI. 3 Kivy Button. 4 Kivy Label. 5 Kivy RecycleView. More items

How to change the background color of the kivybutton?

You can change the color by specifying the background_color property in the format (r, g, b, a). The code demonstrated below: from kivy.app import App from kivy.uix.button import Button class KivyButton (App): def build (self): return Button (text="Welcome to LikeGeeks!", background_color= (155,0,51,53)) KivyButton ().run ()

How do I get Started with Kivy?

We will discuss how to play with the Kivy buttons, labels, recycle view, scroll view, Kivy Canvas, and other widgets to become familiar with the library. You can design Kivy widgets using an intermediate language called Kv language as you’ll see later. Before getting started with Kivy, a basic knowledge of Python programming basics is needed.

How to add text to Kivy GUI?

You can use a label to add text to our GUI. Kivy label supports ASCII and Unicode strings only. You can change the font size of the label using the font_size property:


3 Answers

Use publisher/consumer model as described here. Here's an example from that link modified to use separate threads:

from kivy.app import App
from kivy.clock import Clock, _default_time as time  # ok, no better way to use the same clock as kivy, hmm
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.button import Button
from kivy.properties import ListProperty

from threading import Thread
from time import sleep

MAX_TIME = 1/60.

kv = '''
BoxLayout:
    ScrollView:
        GridLayout:
            cols: 1
            id: target
            size_hint: 1, None
            height: self.minimum_height

    MyButton:
        text: 'run'

<MyLabel@Label>:
    size_hint_y: None
    height: self.texture_size[1]
'''

class MyButton(Button):
    def on_press(self, *args):
        Thread(target=self.worker).start()

    def worker(self):
        sleep(5) # blocking operation
        App.get_running_app().consommables.append("done")

class PubConApp(App):
    consommables = ListProperty([])

    def build(self):
        Clock.schedule_interval(self.consume, 0)
        return Builder.load_string(kv)

    def consume(self, *args):
        while self.consommables and time() < (Clock.get_time() + MAX_TIME):
            item = self.consommables.pop(0)  # i want the first one
            label = Factory.MyLabel(text=item)
            self.root.ids.target.add_widget(label)

if __name__ == '__main__':
    PubConApp().run()
like image 179
Nykakin Avatar answered Nov 12 '22 20:11

Nykakin


BE WARNED: While modifying a kivy property from another thread nominally works, there is every indication that this is not a thread safe operation. (Use a debugger and step through the append function in the background thread.) Altering a kivy property from another thread states that you should not modify a property in this way.

like image 36
mpwhsv Avatar answered Nov 12 '22 20:11

mpwhsv


I think it's worth providing a 2022 update. Kivy apps can now be run via Python's builtin asyncio library and utilities. Previously, the problem was there was no way to return control to the main Kivy event loop when an async function call finished, hence you could not update the GUI. Now, Kivy runs in the same event loop as any other asyncio awaitables (relevant docs).

To run the app asynchronously, replace the YourAppClass().run() at the bottom of your main.py with this:

import asyncio

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(
        YourAppClass().async_run()
    )
    loop.close()

And that's about it. With regards to the docs:

It is fully safe to interact with any kivy object from other coroutines running within the same async event loop. This is because they are all running from the same thread and the other coroutines are only executed when Kivy is idling.

Similarly, the kivy callbacks may safely interact with objects from other coroutines running in the same event loop. Normal single threaded rules apply to both case.

If explicitly need to create a new thread, @Nykakin 's approach is what you want. I'd recommend using Queues to pass data between threads, instead, because they're simpler to implement and more robust, being specifically designed for this purpose. If you just want asynchronicity, async_run() is your best friend.

like image 26
PyUnchained Avatar answered Nov 12 '22 18:11

PyUnchained