Updating a wxPython progress bar after calling app.MainLoop() - python

I have a python script that performs a calculation, and I have created a class for a pop-up wxPython progress bar. Currently I have:
app=wx.App()
progress = ProgressBar()
app.MainLoop()
for i in xrange(len(toBeAnalysed)):
analyse(toBeAnalysed[i])
progress.update(i/len(toBeAnalysed)*100)
Now, this example doesn't work for obvious reasons. Is there any way I can run the app.MainLoop() in a different thread but still communicate the progress (and .update() it) as the calculations are completed?
Thanks for the help.

You should run your logic in a background thread and use wx.CallAfter to periodically update the GUI. CallAfter will invoke the provided function on the GUI thread, so it is safe to make GUI calls.
import wx
import threading
import time
def do_stuff(dialog): # put your logic here
for i in range(101):
wx.CallAfter(dialog.Update, i)
time.sleep(0.1)
wx.CallAfter(dialog.Destroy)
def start(func, *args): # helper method to run a function in another thread
thread = threading.Thread(target=func, args=args)
thread.setDaemon(True)
thread.start()
def main():
app = wx.PySimpleApp()
dialog = wx.ProgressDialog('Doing Stuff', 'Please wait...')
start(do_stuff, dialog)
dialog.ShowModal()
app.MainLoop()
if __name__ == '__main__':
main()

Related

Execute long running code at program startup after complete GUI is rendered

I have an application that can be started from the command line with an optional filename as argument. If present this file should be loaded at startup. Since the processing of the file takes some time, fileOpen() blocks the program and shows a loading indicator.
During normal operation this is ok. However, when I try to do the same at startup (as shown below), the outline of the window is present after show() but its contents is not rendered up until app.exec_().
My Question: How do I handle such a situation?
I cannot put fileOpen() before app.exec_() because then the GUI is not yet rendered completely. And I cannot inform the user that the loading is still processed.
I cannot put ? fileOpen() after app.exec_() because it would not be executed untill the program finishes.
Example Code:
def main(args):
app = QtGui.QApplication()
mainwindow = MainWindow()
mainwindow.show()
if args.filename:
mainwindow.fileOpen(args.filename)
ret_val = app.exec_()
sys.exit(ret_val)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('filename', help='(optional) file to load at startup')
args = parser.parse_args()
main(args)
I have found that a single-shot timer can solve this problem, but I have only tested it on Linux with the Openbox window manager, so I cannot guarantee that it will work on all platforms. You may need to adjust the duration of the timeout to get this to work on your system.
Here is a simple demo that works for me:
import sys
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.edit = QtWidgets.QTextEdit(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.edit)
def fileOpen(self, path):
QtWidgets.qApp.setOverrideCursor(QtCore.Qt.WaitCursor)
QtCore.QThread.sleep(3)
self.edit.setText(open(path).read())
QtWidgets.qApp.restoreOverrideCursor()
def main():
app = QtWidgets.QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.setGeometry(600, 100, 300, 200)
mainwindow.show()
QtCore.QTimer.singleShot(50, lambda: mainwindow.fileOpen(__file__))
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Note: This solution does not work for Linux(X11) (see comment and answer by ekhumoro)
Thanks for all the answers. While each of them has some drawbacks (will discuss it below), they brought me to the correct solution:
Call qApp.processEvents() after mainwindow.show():
def main(args):
app = QtGui.QApplication()
mainwindow = MainWindow()
mainwindow.show()
qApp.processEvents()
if args.filename:
mainwindow.fileOpen(args.filename)
ret_val = app.exec_()
sys.exit(ret_val)
Reason: It's exactly what we want to do:
We want to process events related to the drawing of the main window.
Then execute our custom code.
Then continue with the normal event loop.
Discussion of the alternative suggestions:
Why I don't call qApp.processEvents() in fileOpen(): This will be active for all fileOpen() calls. Processing other events during a long running file open call, may result in unexpected behavior if the application is not designed with this in mind, e.g. you could issue a second fileOpen() while the first is running.
Why I don't use a timer to perform fileOpen(): I want to execute the code after the GUI is completely loaded but before any user input. A timer just approixates the correct execution order. Additionally, the correct delay can vary depending on CPU, system usage and other factors, making this solution not very robust.

Thread won't let Tkinter application close [duplicate]

My app has the following structure:
import tkinter as tk
from threading import Thread
class MyWindow(tk.Frame):
... # constructor, methods etc.
def main():
window = MyWindow()
Thread(target=window.mainloop).start()
... # repeatedly draw stuff on the window, no event handling, no interaction
main()
The app runs perfectly, but if I press the X (close) button, it closes the window, but does not stop the process, and sometimes even throws a TclError.
What is the right way to write an app like this? How to write it in a thread-safe way or without threads?
Main event loop should in main thread, and the drawing thread should in the second thread.
The right way to write this app is like this:
import tkinter as tk
from threading import Thread
class DrawingThread(Thread):
def __init__(wnd):
self.wnd = wnd
self.is_quit = False
def run():
while not self.is_quit:
... # drawing sth on window
def stop():
# to let this thread quit.
self.is_quit = True
class MyWindow(tk.Frame):
... # constructor, methods etc.
self.thread = DrawingThread(self)
self.thread.start()
on_close(self, event):
# stop the drawing thread.
self.thread.stop()
def main():
window = MyWindow()
window.mainloop()
main()

Run pyQT GUI main app in seperate Thread

I am trying to add a PyQt GUI console in my already established application. But the PyQt GUI blocks the whole application making it unable to do rest of the work. I tried using QThread, but that is called from the mainWindow class. What I want is to run the MainWindow app in separate thread.
def main()
app = QtGui.QApplication(sys.argv)
ex = Start_GUI()
app.exec_() #<---------- code blocks over here !
#After running the GUI, continue the rest of the application task
doThis = do_Thread("doThis")
doThis.start()
doThat = do_Thread("doThat")
doThat.start()
My application already uses Python Threads, So my question is, what is the best approach to achieve this process in a threaded form.
One way of doing this is
import threading
def main()
app = QtGui.QApplication(sys.argv)
ex = Start_GUI()
app.exec_() #<---------- code blocks over here !
#After running the GUI, continue the rest of the application task
t = threading.Thread(target=main)
t.daemon = True
t.start()
doThis = do_Thread("doThis")
doThis.start()
doThat = do_Thread("doThat")
doThat.start()
this will thread your main application to begin with, and let you carry on with all the other stuff you want to do after in the code below.

Updating QtGui elements from python

I wrote a small application using QT and Python.
I Press a button, a wait for serial input for 5 seconds. I have few labels
which I want to update e.g., when I press button it should change to 'starting reading' and when I return it should change to 'reading done'. I use
a simple thread which calls processEvents but it does not gets updated and when read function finishes I see the last label change.
class MyWindow(QtGui.QMainWindow):
def __init__(self):
print 'myWindow'
super(MyWindow, self).__init__()
uic.loadUi('test1.ui', self)
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL ('clicked()'), self.buttonStartClicked)
self.show()
def buttonStartClicked(self):
thread = threading.Thread(target = self.update_gui, args = ())
thread.daemon = True
thread.start()
self.label.setText('Starting Test')
response = sRS232_Con.read()
#QtGui.QApplication.processEvents()
self.label.setText('Ending Test')
def update_gui(self):
while True :
QtGui.QApplication.processEvents()
print 'update'
time.sleep(1)
def main():
app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In Qt any QObject that is managed by some event loop should not be touch by other thread (at least without any protection).
If for some reason you cannot use QSerialPort library that provides asynchronous API for serial port it is possible to use QThread for separate thread managed by Qt signals and slots.
Events for QtGui are dispatched by the main thread (insided of the loop app.exec_()).
Another QThread can work with serial port and it can emit signals on 'Starting Test' and on 'Ending Test'. Those signals can be connected to MyWindow slots that are able to update UI in the main thread.

Stopping a WxPython app's main loop from a thread requires window focus

I have a command line utility that starts and stops a WxPython app instance. I'm starting the app in a thread so that I can continue to run commands as well as use the gui app.
My problem is that when I stop the app the console hangs until I focus the app window, then it closes. I'm not sure why the app requires focus to exit.
The app thread looks like this:
class RunAppThread(threading.Thread):
def run(self):
self.app = wx.App(redirect=False)
self.frame = AppWindow(None)
self.app.MainLoop()
def stop(self):
self.app.ExitMainLoop()
The relevant CLI actions are:
def startapp(self):
self.app = gui.RunAppThread()
self.app.start()
def stopapp(self):
self.app.stop()
self.app.join()
Is there a better/correct way to exit the main loop rather than calling wx.App.ExitMainLoop()?
After calling wx.App.ExitMainLoop, use wx.WakeUpMainThread to get the main thread to process events.

Categories