How to interrupt an executing function in kivy? - python

I am developing an app in Kivy and have one function that seems to take a long time to finish. Therefore, when the button that calls this function is pressed, i first open a modalview with a progress bar. I now want to update the value in the progressbar every 500ms while the main function is executing
My first attempt was to use Clock.schedule_interval(). Pseudo code looks something like this:
Class MainClass(Screen):
def button_callback_function(self):
#open modalview with progress bar
view = ModalView(autodismiss=True)
view.open()
Clock.schedule_interval(self.update_progress_bar_in_modalview,0.5)
self.function_that_takes_too_long()
def function_that_takes_too_long(self):
/* code here*/
def update_progress_bar_in_modalview(self,dt):
/*updates value of progress bar*/
With this code, the progress bar does indeed get updated but only after function_that_takes_too_long() finishes, not parallel to it.
My second attempt was to use python threads:
Class MainClass(Screen):
def button_callback_function(self):
#open modalview with progress bar
view = ModalView(autodismiss=True)
view.open()
x=threading.Thread(target=self.update_progress_bar_in_modalview)
x.start()
self.function_that_takes_too_long()
def function_that_takes_too_long(self):
/* code here*/
def update_progress_bar_in_modalview(self):
/*updates value of progress bar*/
timer.sleep(0.5)
Here the second thread to update the progress bar is never even started. It seems the main thread has to pause to give the second thread a chance to start.
So is there any way to call update_progress_bar_in_modalview() while function_that_takes_too_long() is still executing? Something like periodic interrupts in micro controllers. Or maybe start the thread that updates the progress bar on a separate core?
I appreciate any hints.
Alex

Related

How do display a progress bar for each thread

I have a class that, calls a method from other and execute it in parallel, the function that is executed has a for loop that iterates over a dataframe. How can I display a progress bar for each thread?
class App:
otherClass = OtherClass()
n_cpus = os.cpu_count()
def call_multiprocessing(self):
with mp.Pool(processes=self.n_cpus) as executor:
for result in executor.map(otherClass.some_method, self.dataframes):
pass
class OtherClass()
def some_method(self, df):
for index, row in tqdm(dataframe.iterrows(), desc="Progress: ", total= len(df)):
#For each thread that this function is executing
#I want to display a progress bar and keep her in the screen
The way that I'm doing each time a progress bar from a thread is updated it replaces the one that is in console. I want to show then simultaneously, is it possible? Example: If the PC has 4 CPUs, 4 progress bars.
Progress bars should only run in the main thread. Use a queue or similar to pass the status to the main thread and display from there.
Here is an example using Enlighten and multiprocessing, but you should be able to achieve something similar using the threading and queue modules.

How do I set a Progress bar for a certain function execution in PyQt GUI?

I've designed a GUI and after a button click, I am implementing a function named librosa.effects.hpss(), which is taking a lot of time for execution. As it is taking a lot of time for execution, I want to see the execution(with start and done indication also) of that function using a progress bar onto the GUI itself. How do I do it?
I've tried these implementations https://www.themarketingtechnologist.co/progress-timer-in-python/, but they are all for the for() loop implementations, which I don't have any.
This is my code for the function call.
def hpssop(self):
self.file = "file.wav"
self.y, self.sr = librosa.load(self.file)
self.margin_harms = self.margin_har.value()
self.margin_pers = self.margin_per.value()
self.harmonic, self.percussive = librosa.effects.hpss(self.y,
margin=(self.margin_harms, self.margin_pers))
librosa.output.write_wav("harmonic.wav", self.harmonic, self.sr)
librosa.output.write_wav("percussive.wav", self.percussive, self.sr)
def onseparateclick(self):
self.hpss.clicked.connect(self.hpssop)
This is the GUI but instead of separating status I want progress bar

Tkinter progress bar with Excel

We have a Pyxll app (Excel app written in python) that makes a bunch of requests to get data when the workbook is opened. We would like to display a loading bar to the user while the requests are being made and update the loading bar after each request returns.
I'm trying to use Tkinter to do this, but have run into issues. I can get a progress bar to pop up, but it blocks Excel from running until you close the window for the progress bar. I can't put it in a different thread because I want to be able to update the progress based on when the HTTP requests return. Is there an easy way to do this?
Here is my code so far. I have made a basic loading bar class:
import Tkinter as tk
import ttk
class OrderingProgressBar(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# progress goes from 0-100 (for percentage)
self.progress = 0
self.max_progress = 100
self.progress_bar = ttk.Progressbar(self, orient="horizontal", length=200, mode="determinate", maximum=self.max_progress)
self.progress_bar.pack()
And then I have a macro that gets called to launch the app and start making requests.
def launch_ordering_terminal(ribbon):
"""Launch all of the apps required for the Ordering Terminal"""
ordering_progress_bar = OrderingProgressBar()
ordering_progress_bar.mainloop()
excel_utils.turn_off_excel_updates(xl_app())
launch_allocation_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_si_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_accounting_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_reports_builder(terminal_mode)
ordering_progress_bar.progress_bar.step(25)
excel_utils.turn_on_excel_updates(xl_app())
With this code, when I call the macro a loading bar pops up, but block Excel. If I close the window it continues. If I move the mainloop call to the end of launch_ordering_terminal, then the entire terminal loads, and then it pops up the loading bar. I can't get them to work at the same time.
Thanks in advance for the help!
Maintaining a GUI is a full time job that requires an endless loop, called the "mainloop". If you want to do something else in the meantime, you need to add it to the mainloop, or do it in a different thread. In your case I think a new thread would be easiest. Tkinter runs best in the main thread, so put your code in a child thread.
As a totally untested guess:
from threading import Thread
def do_stuff(ordering_progress_bar):
excel_utils.turn_off_excel_updates(xl_app())
launch_allocation_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_si_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_accounting_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_reports_builder(terminal_mode)
ordering_progress_bar.progress_bar.step(25)
excel_utils.turn_on_excel_updates(xl_app())
ordering_progress_bar.quit() # kill the GUI
def launch_ordering_terminal(ribbon):
"""Launch all of the apps required for the Ordering Terminal"""
ordering_progress_bar = OrderingProgressBar()
t = Thread(target=do_stuff, args=(ordering_progress_bar,))
t.start()
ordering_progress_bar.mainloop()

Runtime progress status (like statusbar) in python

I'm trying to create simple progress status label for my Pyqt5 Python code and update it after every iteration of a loop in which a function does a bunch of stuff. The label I want to be updated is "status_label_counter". The below code only shows the part for creating labels and the exact place where I want to use the functionality I've mentioned.
#initialisation code, etc...
self.status_label_counter = QLabel()
self.status_label_from = QLabel(' from: ')
self.status_label_total = QLabel()
status_hbox = QHBoxLayout()
status_hbox.addStretch()
status_hbox.addWidget(self.status_label_counter)
status_hbox.addWidget(self.status_label_from)
status_hbox.addWidget(self.status_label_total)
status_hbox.addStretch()
#bunch of other code...
def create_ics(self):
counter = 0
self.status_label_total.setText(str(len(self.groups)))
for group in self.groups:
#does a bunch of stuff inside
group_manager.create_calendar_for(self.rows, group, self.term)
counter += 1
#for console output
print('iteration: ', counter)
#trying to update status counter
self.status_label_counter.setText(str(counter))
The problem is that I only see the update of both labels when the loop is done with the nested function. When I click a button that calls for "create_ics" function window becomes inactive for about 5 seconds, I see logs on console with count of iterations, but nothing happens in view.
The view (Qt) is locked in your main thread and never gets its chance to process its event loop and thus redraw itself. If you really want to do it this way, call:
self.status_label_counter.repaint()
After you set the text (and if you have some complex layout measuring call QApplication.processEvents() instead).
However, much better option would be to run your create_ics() function in a separate thread leaving your main thread to deal with the view and Qt's event processing. You can do it either through standard Python's threading module, or using Qt's own QThread: https://nikolak.com/pyqt-threading-tutorial/ .

Update progress bar from sub-routine

I would like to update a progress bar that I have on a main window with the progress of a task that I am doing on another sub-routine, would it be possible??
To be as clear as possible, I would have 2 files:
In my Mainwindow.py I would have something like:
import Calculations
#some code
self.ui.progressBar
Calculations.longIteration("parameters")
Then I would have a separate file for the calculations: Calculations.py
def longIteration("parameters")
#some code for the loop
"here I would have a loop running"
"And I would like to update the progressBar in Mainwindow"
Is that possible?
Or it should be done on a different way?
Thanks.
The simplest of methods would be to simply pass the GUI object:
self.ui.progressBar
Calculations.longIteration("parameters", self.ui.progressBar)
and update progressBar on Calculations. This has two problems, though:
You're mixing GUI code with Calculations, who probably shouldn't know anything about it
if longIteration is a long running function, as its name implies, you're blocking your GUI main thread, which will make many GUI frameworks unhappy (and your application unresponsive).
Another solution is running longIteration in a thread, and pass a callback function that you use to update your progress bar:
import threading
def progress_callback():
#update progress bar here
threading.Thread(target=Calculations.longIteration, args=["parameters", progress_callback]).run()
then, inside longIteration, do:
def longIteration( parameters, progress_callback ):
#do your calculations
progress_callback() #call the callback to notify of progress
You can modify the progress_callback to take arguments if you need them, obviously

Categories