Frage

I have an app that needs to add lots of widgets dynamically. Here's a working app to simulate this:

from threading import Thread

from kivy.app import App
from kivy.uix.stacklayout import StackLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.clock import Clock

class LotsOfWidgets(App):
    def build(self):
        self.widgets_amt = 5000

        root = GridLayout(cols=1)
        self.label_container = StackLayout()

        generate_button = Button(text='Generate lots of labels', 
                              size_hint_y=None, height=44)

        generate_button.bind(on_press=self.generate_lots_of_labels)

        hooray_button = Button(text='Print hooray', 
                               size_hint_y=None, height=44)

        hooray_button.bind(on_press=self.print_hooray)

        for widget in (generate_button, hooray_button, 
                       self.label_container):
            root.add_widget(widget)

        return root

    def generate_lots_of_labels(self, *args):
        for _ in xrange(self.widgets_amt):
            label = Label(text='a', size_hint=(None, None), size=(10,10))
            self.label_container.add_widget(label)

    def scheduled_generate_lots_of_labels(self, *args):
        Clock.schedule_once(self.generate_lots_of_labels)

    def threaded_generate_lots_of_labels(self, *args):
        thread = Thread(target=self.generate_lots_of_labels)
        thread.start()

    def print_hooray(self, *args):
        print 'hooray'

LotsOfWidgets().run()

We have a grid layout that has 2 buttons and a stack layout. By clicking the first button, 5000 labels will be generated inside the stack layout. The second button only prints "hooray" to the console.

Adding 5000 widgets to the stack layout and drawing them on the screen takes quite a bit of time which is fine. When you press the button to generate them and immediately press the "print hooray" button, on my computer hooray gets printed about 3 seconds later after the labels appear on the screen. So the problem is, that the UI becomes unresponsive while generating the labels.

I tried to solve this with threading by binding generate_button.on_press to scheduled_generate_lots_of_labels and threaded_generate_lots_of_labels (of course not at the same time) methods instead the one shown in the code, but they don't seem to help.

Is there anything you can do to keep the UI responsive even if it's generating all those widgets?

War es hilfreich?

Lösung

You could add the labels in batches scheduled by Kivy's Clock module.

Andere Tipps

I looked into the idea that brousch gave in his answer. What I tried first was to split those 5000 items into smaller chunks and iterating each chunk in its own Clock.schedule_once.

The result turned out to be very similar to just scheduling once the whole shebang of 5000 items. If you schedule it to be executed in 1 second, you have 1 second to click the hooray button. After that UI becomes unresponsive until all the widgets have been generated.

So in most cases, the only option is to use Clock.schedule_interval and here's an experiment of that. The build method remains the same.

def chunk(self, l, n):
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

def generate_lots_of_labels(self, *args):
    n = 500
    interval = 0.01

    self.chunks = list(self.chunk(range(self.widgets_amt), n))
    self.i = 0

    Clock.schedule_interval(self.iterate, interval)

def iterate(self, *args):
    for _ in self.chunks[self.i]:
        label = Label(text='a', size_hint=(None, None), size=(10,10))
        self.label_container.add_widget(label)

    self.i += 1

    if self.i >= len(self.chunks):
        Clock.unschedule(self.iterate)

This is a compromise between the widget generation speed and responsiveness of the UI. Depending on the application and executing environment, different values of scheduling interval and n give the best result.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top