Skip to content
Advertisement

How to update window and simultaneously run a background process?

New to Python and Kivy. The issue might be not understanding how the main thread of Kivy works, or how to add to the main thread.

The following is made-up code to demonstrate the current problem.

import os
import threading
import http.server
import socketserver
from time import sleep

import kivy
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition


Builder.load_string("""
<BootstrapScreen>:
    name: 'BootstrapScreen'

    Label:
        ProgressBar:
            id: progress_bar
            max: 5
            size: root.width / 2, 300
            center_x: self.parent.center_x
            center_y: self.parent.center_y

<StartScreen>:
    name: 'StartScreen'

    Label:
        ProgressBar:
            id: progress_bar
            max: 5
            size: root.width / 2, 300
            center_x: self.parent.center_x
            center_y: self.parent.center_y
""")


class BootstrapScreen(Screen):

    progress_bar_value = NumericProperty(0)

    def on_enter(self):
        Clock.schedule_interval(self.update_progress_bar, 1)
        Clock.schedule_once(self.start_server, 0)

    def update_progress_bar(self, dt):
        if self.progress_bar_value < 5:
            self.progress_bar_value += 1
        else:
            self.login()
            return False
        self.ids.progress_bar.value = self.progress_bar_value

    def start_server(self, dt):
        sleep(10)
        self.host = '127.0.0.1'
        self.port = 9999

        self.server = socketserver.TCPServer(
                (self.host, self.port),
                http.server.SimpleHTTPRequestHandler
        )
        self.server_thread = threading.Thread(target=self.server.serve_forever)
        self.server_thread.daemon = True
        self.server_thread.start()

    def login(self):
        App().stop()


class StartScreen(Screen):
    progress_bar_value = NumericProperty(0)

    def on_enter(self):
        Clock.schedule_interval(self.update_progress_bar, 1)

    def update_progress_bar(self, dt):
        if self.progress_bar_value < 5:
            self.progress_bar_value += 1
        else:
            self.bootstrap()
            return False
        self.ids.progress_bar.value = self.progress_bar_value

    def bootstrap(self):
        if not self.manager.has_screen('BootstrapScreen'):
            self.manager.add_widget(BootstrapScreen())
        self.manager.current = 'BootstrapScreen'


class DuoApp(App):
    def build(self):
        screen_manager = ScreenManager(transition=NoTransition())
        screen_manager.add_widget(StartScreen())
        return screen_manager


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

The application starts on a first screen. On the first screen is a progress bar that starts automatically and, in about five seconds, the application goes to the second screen. On the second screen, there is another progress bar that is supposed to start immediately and also last for about five seconds, but it waits until after the server starts before beginning. (The sleep(10) is there solely for dramatic affect to demonstrate how the progress bar does not start immediately upon entering the screen, and because the actual process takes about ten seconds to complete.)

How would one go about making the progress bar update in the window at the same time as start_server (or any other background process that takes ten seconds to complete) in the background?

Advertisement

Answer

Via adywizard over on Reddit

# import os
import threading
import http.server
import socketserver
from time import sleep

# import kivy
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition


Builder.load_string("""
<BootstrapScreen>:
    name: 'BootstrapScreen'

    Label:
        ProgressBar:
            id: progress_bar
            max: 5
            size: root.width / 2, 300
            center_x: self.parent.center_x
            center_y: self.parent.center_y

<StartScreen>:
    name: 'StartScreen'

    Label:
        ProgressBar:
            id: progress_bar
            max: 5
            size: root.width / 2, 300
            center_x: self.parent.center_x
            center_y: self.parent.center_y
""")


class BootstrapScreen(Screen):

    progress_bar_value = NumericProperty(0)

    def on_enter(self):
        Clock.schedule_interval(self.update_progress_bar, 1)
        # Clock.schedule_once(self.start_server, 0)
        # Create a separate thread instead.
        threading.Thread(
            target=self.start_server, daemon=True
        ).start()

    def update_progress_bar(self, dt):
        if self.progress_bar_value < 5:
            self.progress_bar_value += 1
        else:
            self.login()
            return False
        self.ids.progress_bar.value = self.progress_bar_value

    def start_server(self):
        sleep(10)
        self.host = '127.0.0.1'
        self.port = 9999

        self.server = socketserver.TCPServer(
                (self.host, self.port),
                http.server.SimpleHTTPRequestHandler
        )
        self.server_thread = threading.Thread(target=self.server.serve_forever)
        self.server_thread.daemon = True
        self.server_thread.start()

    def login(self):
        App().stop()


class StartScreen(Screen):
    progress_bar_value = NumericProperty(0)

    def on_enter(self):
        Clock.schedule_interval(self.update_progress_bar, 1)

    def update_progress_bar(self, dt):
        if self.progress_bar_value < 5:
            self.progress_bar_value += 1
        else:
            self.bootstrap()
            return False
        self.ids.progress_bar.value = self.progress_bar_value

    def bootstrap(self):
        if not self.manager.has_screen('BootstrapScreen'):
            self.manager.add_widget(BootstrapScreen())
        self.manager.current = 'BootstrapScreen'


class DuoApp(App):
    def build(self):
        screen_manager = ScreenManager(transition=NoTransition())
        screen_manager.add_widget(StartScreen())
        return screen_manager


if __name__ == '__main__':
    DuoApp().run()
Advertisement