Display Stderr In A Pyqt QMessageBox
Solution 1:
I worked out something which seems to do the job and is as simple as I can make it. If anyone has improvements or can explain the problems I've worked around here please edit the code.
This is an example which starts a simple main window with a button to create a stderr. The error message is captured and displayed in a message box which clears correctly on 'OK' or closing. The technique can be used with stdout too.
from PyQt4 import QtGui
class StdErrHandler():
def __init__(self):
# To instantiate only one message box
self.err_box = None
def write(self, std_msg):
# All that stderr or stdout require is a class with a 'write' method.
if self.err_box is None:
self.err_box = QtGui.QMessageBox()
# Both OK and window delete fire the 'finished' signal
self.err_box.finished.connect(self.clear)
# A single error is sent as a string of separate stderr .write() messages,
# so concatenate them.
self.err_box.setText(self.err_box.text() + std_msg)
# .show() is used here because .exec() or .exec_() create multiple
# MessageBoxes.
self.err_box.show()
def clear(self):
# QMessageBox doesn't seem to be actually destroyed when closed, just hidden.
# This is true even if destroy() is called or if the Qt.WA_DeleteOnClose
# attribute is set. Clear text for next time.
self.err_box.setText('')
class AppMainWindow(QtGui.QMainWindow):
"""
Main window with button to create an error
"""
def __init__(self, parent=None):
# initialization of the superclass
super(AppMainWindow, self).__init__(parent)
self.create_err = QtGui.QPushButton(self)
self.create_err.setText("Create Error")
self.create_err.clicked.connect(self.err_btn_clicked)
def err_btn_clicked(self):
# Deliberately create a stderr output
oopsie
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
amw = AppMainWindow()
amw.show()
# Instantiate stderr handler class
std_err_handler = StdErrHandler()
# Connect stderr to the handler
sys.stderr = std_err_handler
sys.exit(app.exec_())
EDIT: As three_pineapples pointed out, this isn't thread-safe. I found when I installed this in another app the message box would hang if a stderr occurred while running the app under the pydev debugger, which is probably due to this.
I rewrote it using a thread and queue, but this also hung.
Then I learned that PyQt signals & slots are thread-safe, so I rewrote it using only that. The version below is thread-safe (I think) and runs correctly as a stand-alone script or under the debugger:
from PyQt4 import QtCore, QtGui
class StdErrHandler(QtCore.QObject):
'''
This class provides an alternate write() method for stderr messages.
Messages are sent by pyqtSignal to the pyqtSlot in the main window.
'''
err_msg = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
QtCore.QObject.__init__(self)
def write(self, msg):
# stderr messages are sent to this method.
self.err_msg.emit(msg)
class AppMainWindow(QtGui.QMainWindow):
'''
Main window with button to create an error
'''
def __init__(self, parent=None):
# initialization of the superclass
super(AppMainWindow, self).__init__(parent)
# To avoid creating multiple error boxes
self.err_box = None
# Single button, connect to button handler
self.create_err = QtGui.QPushButton(self)
self.create_err.setText("Create Error")
self.create_err.clicked.connect(self.err_btn_clicked)
def err_btn_clicked(self):
# Deliberately create a stderr output
oopsie
def std_err_post(self, msg):
'''
This method receives stderr text strings as a pyqtSlot.
'''
if self.err_box is None:
self.err_box = QtGui.QMessageBox()
# Both OK and window delete fire the 'finished' signal
self.err_box.finished.connect(self.clear)
# A single error is sent as a string of separate stderr .write() messages,
# so concatenate them.
self.err_box.setText(self.err_box.text() + msg)
# .show() is used here because .exec() or .exec_() create multiple
# MessageBoxes.
self.err_box.show()
def clear(self):
# QMessageBox doesn't seem to be actually destroyed when closed, just hidden.
# This is true even if destroy() is called or if the Qt.WA_DeleteOnClose
# attribute is set. Clear text for next time.
self.err_box.setText('')
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
amw = AppMainWindow()
amw.show()
# Create the stderr handler and point stderr to it
std_err_handler = StdErrHandler()
sys.stderr = std_err_handler
# Connect err_msg signal to message box method in main window
std_err_handler.err_msg.connect(amw.std_err_post)
sys.exit(app.exec_())
Post a Comment for "Display Stderr In A Pyqt QMessageBox"