I'm currently writing a GUI for rvplayer that shall enable artists to automatically render dailies with slate and burn-in information. The GUI is written with PySide and scripted in Python 2.7. My problem is that upon calling my process and updating my QProgressBar with the stdout the GUI freezes. I know that this is a common problem and that it can probably be solved with processEvents() somehow, but I know far too little about threading and process loops to get my head around this issue. Since my code is a little lengthy already, here's the part that causes the issue:
def rv(self, args):
p = subprocess.Popen(["C:/Program Files/Tweak/RV-4.0.10-64/bin/rvio_hw.exe"]+[x for x in args], stdout=subprocess.PIPE)
while True:
line = p.stdout.readline()
if line != "":
progressStr=re.search(r"([0-9]+.[0-9]+%)", line.rstrip())
if progressStr == None:
print line.rstrip()
else:
progressInt=int(float(re.sub("[^0123456789\.]", "", progressStr.group())))
self.prog_QProgressBar.setValue(progressInt)
print progressStr.group()
else:
break
and here is the part that starts my QApplication:
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
finalForm = MainWindow()
finalForm.show()
sys.exit(app.exec_())
I'm calling the function rv upon pressing a button and although the progress bar keeps updating normally, the window starts to get nonresponsive after some time. I do not understand at which point I could use app.processEvents() to tell my QApplication to run the process in a separate thread or in the background.
Since it looks like you're not using threads, it may be that all that's required is to call processEvents after updating the progress bar, like this:
self.prog_QProgressBar.setValue(progressInt)
QtGui.qApp.processEvents()
However, the effectiveness of this may depend on how long it takes the process to produce the output. All that the processEvents call does is to immediately handle any pending events (e.g. widget painting, mouse clicks, etc) that are currently in the application's event queue. In between those calls, the GUI will continue to freeze (i.e. the executing code is not run in a separate thread or in the background, as you suggested). So the extent to which this technique can keep the GUI responsive depends on how frequently processEvents can be called within the rv() method.
The issue is that it's not as if your app is frozen, but Windows thinks that the app is frozen as it's ignoring events (mouse over, click etc etc), so Windows, in its wisdom, gives you that dialogue.
You need to start the thread off after the show() and then run the processEvents function, and obviously only call sys.exit once your thread has finished.
Related
I borrowed a design that I found on stackoverflow to redirect console output to a PyQt5 GUI textEdit widget. This works fine, but the text is not displayed in "real-time". It seems to output the text to the GUI once a process has completed. This has not been a problem until I tried to use time.sleep(secs) to print something, pause, then print something else. What ends up happening is that the program pauses for secs, then it prints all of the statements at once.
This class is in the mainWindow file for the GUI:
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
This is in the __init__ method of my event handling file:
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.case_setup_console.setReadOnly(True)
self.main_console.setReadOnly(True)
This function is in the main class of event handling file (outside __init__):
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
cursor = self.case_setup_console.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.case_setup_console.setTextCursor(cursor)
self.case_setup_console.ensureCursorVisible()
This works as intended to re-route the output to the text edit widget self.case_setup_console. But, when I try to run a code such as:
print('This is the first message')
time.sleep(5)
print('This should print 5 seconds later')
What happens is that the program waits 5 seconds, then it prints both statements together.
When programing for GUI code, there is a fundamental shift in how the program is designed. To make it short: after building and initialisation, the program is all the time running in an "event loop" provided by the GUI framework, and your code is only called when specific events take place.
That is in contrast with a terminal application where your code is running all the time, and you tell when to do "print"s, "input"s and pauses with "time.sleep".
The GUI code is responsible for taking notes of events (keyboard, UI, network, etc...), redrawing window contents and calling your code in response to events, or just when it is time to redraw a content that is defined in your code (like updating a background image).
So, it can only render the text that is supposed to show up in a window, with your redirected "print", when the control is passed back to its event loop. When you do time.sleep you pause the return - no code in the event loop is run, and it can't, of course, do any screen drawing.
What is needed is that you write your pauses in the program in a way that during the pause, the GUI event loop is running - not "time.sleep", that just suspends your whole thread.
In Qt the way to do that is create a QTimer object to call the code you want to use to print text at a particular moment, and then just surrender the execution to the the QtMainloop by returning from your function.
Thanks to Python's support for nested functions, that can be done in painless ways, even using lambda functions when setting the timer itself.
...
print('This is the first message')
timer = QtCore.QTimer
timer.singleShot(5000, lambda *_: print('This should print 5 seconds later'))
Should work for the given example. (The call, as usual for UIs, takes the pause time in miliseconds rather than in seconds).
If you will need to schedule more text to be printed after each phrase is output, you will need to call the scheduling inside the callback itself, and will need a little more sophistication, but it still could be as simple as:
phrases = iter(("hello", "world", "I", "am", "talking", "slowly!"))
timer = QtCore.QTimer()
def talker(*_):
phrase = next(phrases, None)
if not phrase:
return
print(phrase)
timer.singleShot(1000, talker)
timer.singleShot(1000, talker)
(Note that there is nothing special about the *_ parameter name either: I am just indicating that there might be any number of positional arguments for the callback - (The "*" part, that is Python syntax) - and that I won't care about then (I call the argument sequence as "_" to indicate I don't care how this is called, as it won't be used anyway - that is a coding convention) )
The iter and next calls are more "Python dialect" than one might be used, but one could just use a list and a counter up to the list length all the same.
I'm trying to understand how the cycle of my "main.py" works. It's based on examples found on the net, about the PySide and Qt Designer, to implement a Python GUI.
The code is:
#***********************************#
# Python Libraries #
#***********************************#
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
#***********************************#
# Python files #
#***********************************#
import Gui
from server import *
class MainDialog(QDialog, Gui.Ui_TCPServer):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.connect(self.ConnectBt, SIGNAL("clicked()"), self.ConnectBt_clicked)
self.connect(self.QuitBt, SIGNAL("clicked()"), self.QuitBt_clicked)
self.connect(self.DisconnectBt, SIGNAL("clicked()"), self.DisconnectBt_clicked)
print "NOW HERE\r\n"
def ConnectBt_clicked(self):
self.ConnectBt.setText("Connecting...")
self.server_connect()
print "THEN HERE\r\n"
def QuitBt_clicked(self):
self.close()
def DisconnectBt_clicked(self):
self.ConnectBt.setText("Connect")
self.server_off = ChronoRequestHandler()
self.server_off.finish()
def server_connect(self):
self.server_on = ServerStart()
self.server_on.try_connect()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MainDialog()
print "HERE\r\n"
form.show()
app.exec_()
print "END\r\n"
When I call the "main.py" I get a print of "NOW HERE" and "THEN HERE". When I press the 'ConnectBt', I get the print of "THEN HERE".
But and after this, where the cycle remains? Does it returns to init, if so, shouldn't I get again the print of "NOW HERE"? Does it returns to main, if so, shouldn't I get the print of "HERE"? Please explain me...
When I press the 'QuitBt' I get the print of "END"... I'm confused!
Thanks.
I think you should get more clear on how object programming and events work.
In the last if-statement (the code on the bottom that runs when you call your script from e.g. terminal) you create an app object instance of QApplication.
After that you create form, instance of MainDialog which is the class you define above (inheriting methods, properties, etc from two classes, QDialog, Gui.Ui_TCPServer).
By doing
form = MainDialog()
you run __init__, print "NOW HERE" and go out of that method. Please check what __init__ does in Python. why-do-we-use-init-in-python-classes
Before the end you call the exec() method of the app instance. This contains a loop so that your interface gathers and processes events. See the documentation on QApplication.exec() below.
When you press the 'ConnectBt' button you call the ConnectBt_clicked() method, which does stuff (connects with the server) and prints "THEN HERE".
In the same way, when you press QuitBt you call QuitBt_clicked(), which closes the connection and lets the code print "END".
I also suggest you read more documentation about the classes you are using. They will explain how come that the different buttons are "linked"/have as callbacks the methods ConnectBt_clicked(), def QuitBt_clicked(), and DisconnectBt_clicked(). The mechanisms by which the buttons trigger these callbacks is kind of implicit in the code implemented in those classes.
QApplication Class Reference: exec_
int QApplication.exec_ ()
Enters the main event loop and waits until exit() is called, then
returns the value that was set to exit() (which is 0 if exit() is
called via quit()).
It is necessary to call this function to start event handling. The
main event loop receives events from the window system and dispatches
these to the application widgets.
Generally, no user interaction can take place before calling exec().
As a special case, modal widgets like QMessageBox can be used before
calling exec(), because modal widgets call exec() to start a local
event loop.
To make your application perform idle processing, i.e., executing a
special function whenever there are no pending events, use a QTimer
with 0 timeout. More advanced idle processing schemes can be achieved
using processEvents().
We recommend that you connect clean-up code to the aboutToQuit()
signal, instead of putting it in your application's main() function.
This is because, on some platforms the QApplication.exec() call may
not return. For example, on the Windows platform, when the user logs
off, the system terminates the process after Qt closes all top-level
windows. Hence, there is no guarantee that the application will have
time to exit its event loop and execute code at the end of the main()
function, after the QApplication.exec() call.
See also quitOnLastWindowClosed, quit(), exit(), processEvents(), and
QCoreApplication.exec().
I'm building an application that does thousands (possibly millions) of calculations based off of what the user inputs. Because these calculations can take a long time, I want to use Python's multiprocessing module. Here is my dilemma; I want to set up a tkinter window with a cancel button to stop the calculations running throughout the processors I set up using Pool. I tried using threads to allow the popup window to run, but some funny things happen when I run the code.
When I press the 'start' button, the Pool starts going as expected. However, my popup window does not show even though it is on its own thread. Even weirder, when I tell my IDE (PyCharm) to stop the program, the popup window shows and the calculations are still running until I either press the 'exit' button from the popup window, or kill the program altogether.
So my questions are: Why won't my popup window show even though it is on its own thread? Am I utilizing the multiprocessing module correctly? Or is the problem something totally different?
import tkinter as tk
from tkinter import ttk
import random
import threading
import time
from multiprocessing import Pool
def f(x):
print(x*x)
time.sleep(random.randrange(1,5)) # simulates long calculations
# Extra math stuff here...
def processor():
global calc
calc = Pool(4)
calc.map(f, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])
print("Done")
def calculation_start():
p = threading.Thread(target=progress_window) # Thread to open a new window for the user to cancel a calculation
p.start()
processor() # Calls the function to run the calculations
def progress_window():
# This is the window that pops up to let a user cancel a calculation.
popup = tk.Toplevel()
endButton = ttk.Button(popup, text="End", command=lambda :(calc.terminate()))
endButton.pack()
master = tk.Tk()
startButton = ttk.Button(master, text="Start", command=calculation_start)
startButton.pack()
tk.mainloop()
EDIT:
I tried switching the processor function to a thread instead of the progress_window function.
def calculation_start():
p = threading.Thread(target=processor) # Thread for the function to run the calculations
p.start()
progress_window() # Open a new window for the user to cancel a calculation
My popup window appears and the 'end' button looks like it stops the processor when pressed, but it never continues past that point. It's like it's stuck at calc.map(f, [1,2,3,...] in the processor() function.
From the Pool.map documentation:
It blocks until the result is ready.
That means that, yes, the work is being done on other threads, but the main thread (the one calling map) will not execute anything else until the other threads are complete. You want to use map_async and (for a long-running, interactive process), provide a callback for what to do when the pool is finished (probably hide the pop-up).
If you want your program to simply show the pop-up then exit, you want to use the AsyncResult returned by map_async. Set up an idle handler using after_idle (See tkinter universal widget methods and remember you'll need to manually re-add the handler after every call). After each idle call, you could call either AsyncResult.ready() or AsyncResult.get(<timeout>) to see if the pool has completed. Once it's finished, you're safe to process the results and exit.
We're building a hardware thing based on RaspberryPi and using PyQt4 to handle output on a small 3" screen.
Apart from screen we have a simple hardware keypad that needs handling in a loop to check for keys pressed and act accordingly.
To simplify things code right now looks something like this:
while True:
gui_initialized = False
current_screen = None
if gui_initialized is False:
app = QtGui.QApplication(sys.argv)
main_win = gui.GUI()
gui_initialized = True
sys.exit(app.exec_())
key_code = kp.getKey()
This code is wrong because of the PyQt4 main loop. Once execution gets to the line sys.exit(app.exec_()) - it stops in there forever and never gets to key_code = kp.getKey(). But I need key_code = kp.getKey() to execute somewhere within the loop to handle keypad and also will need to do other background work.
In fact usual Qt event handling is not really applicable in our case because we'll have no standard input tools, only a custom keypad and NFC antenna, both handled within main python program.
So the question is - how do I handle additional work I need to do along with PyQt4 main loop?
The idiomatic way of doing polling in Qt is to put your polling code in a slot, and connect to it a timeout signal from a QTimer with a zero timeout.
I have a program in Python that gets a window handle via COM from another program (think of the Python program as an addin) I set this window to be the main Python frame's parent so that if the other program minimizes, the python frame will too. The problem is when I go to exit, and try to close or destroy the main frame, the frame.close never completes it's execution (although it does disappear) and the other program refuses to close unless killed with TaskManager.
Here are roughly the steps we take:
if we are started directly, launch other program
if not, we are called from the other program, do nothing
enter main function:
create new wx.App
set other program as frame parent:
Get handle via COM
create a parent using wx.Window_FromHWND
create new frame with handle as parent
show frame
enter main loop
App.onexit:
close frame
frame = None
handle as parent = None
handle = None
Anybody have any thoughts on this or experience with this sort of thing?
I appreciate any help with this!
[Edit]
This is only the case when I use the handle as a parent, if I just get the handle and close the python program, the other program closes fine
I wonder if your Close call may be hanging in the close-handler. Have you tried calling Destroy instead? If that doesn't help, then the only solution would seem to be "reparenting" or "detaching" your frame -- I don't see a way to do that in wx, but maybe you could drop down to win32 API for that one task...?
If reparenting is all you need, you can try frame.Reparent(None) before frame.Close()
My resolution to this is a little bit hacked, and admittedly not the most elegant solution that I've ever come up with - but it works rather effectively...
Basically my steps are to start a thread that polls to see whether the window handle is existent or not. While it's still existent, do nothing. If it no longer exists, kill the python application, allowing the handle (and main application) to be released.
class CheckingThread(threading.Thread):
'''
This class runs a check on Parent Window to see if it still is running
If Parent Window closes, this class kills the Python Window application in memory
'''
def run(self):
'''
Checks Parent Window in 5 seconds intervals to make sure it is still alive.
If not alive, exit application
'''
self.needKill = False
while not self.needKill:
if self.handle is not None:
if not win32gui.IsWindow(self.handle):
os._exit(0)
break
time.sleep(5)
def Kill(self):
'''
Call from Python Window main application that causes application to exit
'''
self.needKill = True
def SetHandle(self, handle):
'''
Sets Handle so thread can check if handle exists.
This must be called before thread is started.
'''
self.handle = handle
Again, it feels a little hackish, but I don't really see another way around it. If anybody else has better resolutions, please post.