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
Related
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
I would like to make it so that I can see the progress of the execution of the loop, but the ProgressBar only exits after the execution of this loop. How can I fix this?
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.progressbar import ProgressBar
class MyApp(App):
def build(self):
self.wid = FloatLayout()
self.prog_bar = ProgressBar(max=99999, pos = [0, -150])
self.wid.add_widget(self.prog_bar)
self.prog_bar.value = 0
for i in range(0, 99999):
self.prog_bar.value = i
print(i/99999)
return self.wid
if __name__ == '__main__':
MyApp().run()
Kivy uses the main thread of your App to update its widgets. You are running your loop on the main thread, so Kivy cannot update the ProgressBar until that loop completes. The fix is to do the loop in another thread:
class MyApp(App):
def build(self):
self.wid = FloatLayout()
self.prog_bar = ProgressBar(max=99999, pos=[0, -150])
self.wid.add_widget(self.prog_bar)
self.prog_bar.value = 0
threading.Thread(target=self.do_progress).start()
return self.wid
def do_progress(self):
for i in range(0, 99999):
self.prog_bar.value = i
print(i / 99999)
This is not about composition, but rather about multitasking, the thing is, if you do a locking loop like this, kivy can’t update anything until the loop is done (because nothing else happens while the function runs, no update of the UI or processing of any event). So what you want to do is allow the loop to run "concurrently" to the kivy event loop.
There are multiple ways to do this kind of things, they are all more or less suited to different situations.
kivy Clock allows scheduling a function to run later (schedule_once), possibly multiple times (schedule_interval), so you could have a more atomic function, that just increments the value of the progress bar once (not in a loop), and call this function using schedule interval. The issue is if you want to do actual work in the function, that could take significant enough time for the UI to be visibly blocked between increment of the progressbar value.
If that’s your case, because the function you are trying to run is slow, then you might want to run the function in a Thread instead, so it doesn’t block the kivy UI. You’ll be able to update the value of your progress bar from the the thread, if necessary, using the Clock to schedule a function doing just that (you need to be careful about not doing things that update the OpenGL context from a thread, as OpenGL doesn’t specify the behavior in such situation, so some graphic cards/drivers can be fine with it, but others will crash your program, so anything updating the UI from a thread must use the clock, there is a mainthread decorator provided in the clock module for that).
In some situation, you can (and thus want) avoid doing the long work beforehand, and find a way to do the minimal amount of work each time needed, and go on with the work, there are some abstractions of that idea, like RecycleView, that avoids creating widgets altogether, when you want a very long list of items to display, just creating the data beforehand, and letting it handle the creation/update of the widgets as needed. Of course, this might not apply to your situation, but it’s worth mentioning in case it is.
So in short: don’t do such loops, or do them in threads, think really hard if you actually need such a loop, and use clock events to update your UI when using threads.
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
I'm creating a GUI to run a fatigue analysis program in Python using PyQt. When the user clicks 'Start' in the main window to start the analysis a function in another file is called, which then chugs away (potentially for hours) until complete.
I'd like to find a way to update the status bar in the main window from within the separate fatigue analysis file. I don't care how I achieve this, but I'd really rather not just copy and paste everything from the fatigue analysis file into the GUI file (in which case I'd know how to update the status bar).
So far I've tried starting the GUI using the code below which is global (MainWindow is a class with all the stuff to set up the GUI, and updateStatus is a method within that class to update the status bar of the GUI):
app = QtGui.QApplication(sys.argv)
TopWindow = MainWindow(LastData)
TopWindow.show()
sys.exit(app.exec_())
In the Fatigue Analysis file I have the following code, which is called within a loop:
TopWindow.updateStatus(PercentComplete)
This throws the error below:
NameError: global name 'TopWindow' is not defined
I don't understand why this doesn't work, but it definitely doesn't! Any ideas for a workaround?
Thanks,
Pete
I believe you misunderstood the scope of a variable. Let me explain.
When you write the following code,
def myfunction():
a = 5
print(a) # OK
b = a # This line fails
you will get a failure, because a is local to myfunction, that is to say it only exists inside that function, and the last statement refers to a variable that is not known to python. The same holds for modules. Variables defined in one module are not accessible to the other modules... Unless you really want to do crappy things.
A nice solution is to think your modules as a main one, calling functionalities from the other ones. These functionalities can be classes or functions, in other words, you will pass the reference to your main window as an argument.
Main module
import fatigue_analysis
class MainWindow(QtGui.Window):
# ....
def start_file_compute(self, path_to_file):
# Pass a reference to self (MainWindow instance) to the helper function
fatigue_analysis.start(path_to_file, self)
# ....
fatigue_analysis.py
def start(path_to_file, top_window):
# ....
top_window.updateStatus(PercentComplete)
# ....
That said, you may consider using QtCore.QThread to run your long computation while leaving the GUI responsive. In this case, beware that Qt do not like very much that one thread tries to modify a member of another thread (i.e. you main window). You should use signals to pass information from on thread to another.
You can do the following:
In the MainWindow class, you should first create a statusbar: self.statusbar = self.statusBar()
Then, from the analysis code, pass a reference to the MainWindow object and from there you can update the statusbar as you wish using this:
main_window_object.statusbar.showMessage(PercentComplete)
EDIT: I do not know what the LastData is, but you would need to inherit from the QtGui.QMainWindow ideally.
I'm doing a program in which I'm using a wxStatusBar, when a download starts I start a child thread like this:
def OnDownload(self, event):
child = threading.Thread(target=self.Download)
child.setDaemon(True)
child.start()
Download is another function without parameters (except self). I would like to update my statusbar from there with some information about the downloading progress, but when I try to do so I often get Xwindow, glib and segfaults errors. Any idea to solve this?
Solved: I just needed to include wx.MutexGuiEnter() before changing something in the GUI inside the thread and wx.MutexGuiLeave() when finished. For example
def Download(self):
#stuff that doesn't affect the GUI
wx.MutexGuiEnter()
self.SetStatusText("This is a thread")
wx.MutexGuiLeave()
And that's all :D
Most people get directed to the wxPython wiki:
http://wiki.wxpython.org/LongRunningTasks
I also wrote up a little piece on the subject here:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
I don't think I've ever seen your solution before though.
How are you updating the status bar?
I think you should be fine if you create a custom event, and then post it via wx.PostEvent to notify the frame/status bar in the GUI thread.
For download progress in a status bar, you might want your event to look something like this:
DownloadProgressEvent, EVT_DL_PROGRESS = wx.lib.newevent.NewEvent()
# from the thread...
event = DownloadProgressEvent(current=100, total=1000, filename="foo.jpg")
wx.PostEvent(frame, event)
# from the frame:
def OnDownloadProgress(self, event):
self.statusbar.update_dl_msg(event.current, event.total, event.filename)
Here's some more detail from the wxPython wiki.