Reputation: 213
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 would like to provide some indication to the user that the process is running. Ideally, this would be in the form of a spinning wheel or loading bar or something, but even if I could update my display to show the user that a process is running, it would be better than what I have now.
Currently, the user presses a button in the main UI. This brings up a popup that verifies some key information with the user, and if they are happy with those options, they press a 'run' button. I have tried opening a new popup to tell them that the process is running, but because the display doesn't update until the process finishes, this doesn't work.
I have a lot of coding experience, but mostly in the context of math and engineering, so I am very new to the designing of UIs and having to handle events and threads. A simple self-contained example would be greatly appreciated.
Upvotes: 10
Views: 14665
Reputation: 1
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()
Upvotes: 0
Reputation: 88
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.
Upvotes: 1
Reputation: 4402
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.
Upvotes: 11