Building a simple progress bar or loading animation in Kivy?

前端 未结 3 973
眼角桃花
眼角桃花 2020-12-15 12:10

I am writing a Kivy UI for cmd line utility I have developed. Everything works fine, but some of the processes can take from a few seconds to a few minutes to process and I

相关标签:
3条回答
  • 2020-12-15 12:25

    I was recently tackling the problem you described: display doesn't update until the process finishes

    Here is a complete example that I got working with the help of @andy_s in the #Kivy IRC channel:

    My main.py:

    from kivy.app import App
    from kivy.uix.popup import Popup
    from kivy.factory import Factory
    from kivy.properties import ObjectProperty
    from kivy.clock import Clock
    
    import time, threading
    
    class PopupBox(Popup):
        pop_up_text = ObjectProperty()
        def update_pop_up_text(self, p_message):
            self.pop_up_text.text = p_message
    
    class ExampleApp(App):
        def show_popup(self):
            self.pop_up = Factory.PopupBox()
            self.pop_up.update_pop_up_text('Running some task...')
            self.pop_up.open()
    
        def process_button_click(self):
            # Open the pop up
            self.show_popup()
    
            # Call some method that may take a while to run.
            # I'm using a thread to simulate this
            mythread = threading.Thread(target=self.something_that_takes_5_seconds_to_run)
            mythread.start()
    
        def something_that_takes_5_seconds_to_run(self):
            thistime = time.time() 
            while thistime + 5 > time.time(): # 5 seconds
                time.sleep(1)
    
            # Once the long running task is done, close the pop up.
            self.pop_up.dismiss()
    
    if __name__ == "__main__":
        ExampleApp().run()
    

    My example.kv:

    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'center'
        Button:
            height: 40
            width: 100
            size_hint: (None, None)
            text: 'Click Me'
            on_press: app.process_button_click()
    
    <PopupBox>:
        pop_up_text: _pop_up_text
        size_hint: .5, .5
        auto_dismiss: True
        title: 'Status'   
    
        BoxLayout:
            orientation: "vertical"
            Label:
                id: _pop_up_text
                text: ''
    

    If you run this example, you can click the Click Me button, which should open up a "progress bar" in the form of a modal/pop-up. This pop up will remain open for 5 seconds without blocking the main window. After 5 seconds, the pop up will automatically be dismissed.

    0 讨论(0)
  • 2020-12-15 12:42

    I've dealt with similar problem and creating new thread didn't do the trick. I had to use Clock.schedule_once(new_func) function. It schedules function call to the next frame, so it is going to run almost immediately after callback ends.

    0 讨论(0)
  • 2020-12-15 12:48

    I had this issue but found the discussion here a bit beyond my understanding.

    I looked at a LOT of answers but nothing made sense until I researched the threading module a bit more. The Kivy support channel on discord also were incredibly helpful so props to them.

    My workaround has been to create a loading screen. I will attach the code here for people who similarly need a simple reproducible code snippet :

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.recycleview import RecycleView
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.properties import ObjectProperty, ListProperty, StringProperty, 
    NumericProperty
    import threading
    import time
    from kivy.clock import mainthread
    
    
    class WindowManager(ScreenManager):
        pass
    
    class WelcomeWindow(Screen):
    
        def generateData(self):
            data = []
            for i in range (100):
                #print(i)
                data.append(i)
            time.sleep(3)
            self.set_screen()
            return data
    
    def executeFunc(self):
        self.manager.current = 'Loading' # Here is where I have tried to move to loading screen while func runs
        t1 = threading.Thread(target=self.generateData)# Here is where I have tried to thread the function
        t1.start()
        #t1.join() #Here is where I have tried to wait until func finished before changing screen
        #self.manager.current = 'Final'
    
    @mainthread
    def set_screen(self):
        self.manager.current = 'Final'
    
    
    
    class LoadingWindow(Screen):
        pass
    
    class FinalWindow(Screen):
        pass
    
    KV = '''
    WindowManager:
        WelcomeWindow:
        LoadingWindow:
        FinalWindow:
    
    <WelcomeWindow>:
        name:'Welcome'
        BoxLayout:
            Label:
                text: "JUST SOME TEXT"
            Button:
                text: "Generate Data"
                font_size: sp(30)
                size_hint: .4,.4
                on_release:
                    root.executeFunc()
                    #app.root.current = "Loading"
                    root.manager.transition.direction = "left"
    
    <LoadingWindow>:
        name: 'Loading'
        BoxLayout:
            Label: 
                text: "LOADING SCREEN"
            Button:
                text: "Go Back"
                on_release:
                    app.root.current = "Welcome"
                    root.manager.transition.direction = "right"
                    
    <FinalWindow>:
        name: 'Final'
        BoxLayout:
            Label: 
                text: "FINISHED"
    '''''
    
    class TestApp(App):
        def build(self):
            return Builder.load_string(KV)
    
    TestApp().run()
    
    0 讨论(0)
提交回复
热议问题