Reputation: 75
I am piping the "stdout" and "stderr" to an embedded QTextBrowser widget in a pyqt5 GUI. All works, but if I pipe a very long output (which happens all the time) the ScrollBar of the widget is always in last upper position, and thus I cant see the new output in a real time. This is really annoying! I tried everything I could find in the internet, but still with no success. It cannot be too hard.... Please open my eyes!
Code I am using is here (I think it was found on the StackOverflow):
import sys
from PyQt4 import QtCore, QtGui
import logging
logger = logging.getLogger(__name__)
class QtHandler(logging.Handler):
def __init__(self):
logging.Handler.__init__(self)
def emit(self, record):
record = self.format(record)
XStream.stdout().write("{}\n".format(record))
handler = QtHandler()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
class XStream(QtCore.QObject):
_stdout = None
_stderr = None
messageWritten = QtCore.pyqtSignal(str)
def flush( self ):
pass
def fileno( self ):
return -1
def write( self, msg ):
if ( not self.signalsBlocked() ):
self.messageWritten.emit(msg)
@staticmethod
def stdout():
if ( not XStream._stdout ):
XStream._stdout = XStream()
sys.stdout = XStream._stdout
return XStream._stdout
@staticmethod
def stderr():
if ( not XStream._stderr ):
XStream._stderr = XStream()
sys.stderr = XStream._stderr
return XStream._stderr
class MyDialog(QtGui.QDialog):
def __init__( self, parent = None ):
super(MyDialog, self).__init__(parent)
self._console = QtGui.QTextBrowser(self)
self._console.moveCursor(QtGui.QTextCursor.End)
layout = QtGui.QVBoxLayout()
layout.addWidget(self._console)
self.setLayout(layout)
XStream.stdout().messageWritten.connect(self._console.insertPlainText)
XStream.stderr().messageWritten.connect(self._console.insertPlainText)
self.pipe_output()
def pipe_output( self ):
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
#print('Old school hand made print message')
if ( __name__ == '__main__' ):
#app = None
# if ( not QtGui.QApplication.instance() ):
app = QtGui.QApplication([])
dlg = MyDialog()
dlg.show()
#if ( app ):
app.exec_()
Here is a print screen
Upvotes: 1
Views: 4201
Reputation: 75
Thanks to titusjan and eyllanesc, I was able to get what I want!
The code that works is:
import sys
from PyQt5 import QtCore, QtGui
import logging
logger = logging.getLogger(__name__)
class QtHandler(logging.Handler):
def __init__(self):
logging.Handler.__init__(self)
def emit(self, record):
record = self.format(record)
XStream.stdout().write("{}\n".format(record))
handler = QtHandler()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
class XStream(QtCore.QObject):
_stdout = None
_stderr = None
messageWritten = QtCore.pyqtSignal(str)
def flush( self ):
pass
def fileno( self ):
return -1
def write( self, msg ):
if ( not self.signalsBlocked() ):
self.messageWritten.emit(msg)
@staticmethod
def stdout():
if ( not XStream._stdout ):
XStream._stdout = XStream()
sys.stdout = XStream._stdout
return XStream._stdout
@staticmethod
def stderr():
if ( not XStream._stderr ):
XStream._stderr = XStream()
sys.stderr = XStream._stderr
return XStream._stderr
class LogMessageViewer(QtGui.QTextBrowser):
def __init__(self, parent=None):
super(LogMessageViewer,self).__init__(parent)
self.setReadOnly(True)
#self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
@QtCore.pyqtSlot(str)
def appendLogMessage(self, msg):
horScrollBar = self.horizontalScrollBar()
verScrollBar = self.verticalScrollBar()
scrollIsAtEnd = verScrollBar.maximum() - verScrollBar.value() <= 10
self.insertPlainText(msg)
if scrollIsAtEnd:
verScrollBar.setValue(verScrollBar.maximum()) # Scrolls to the bottom
horScrollBar.setValue(0) # scroll to the left
class MyDialog(QtGui.QDialog):
def __init__( self, parent = None ):
super(MyDialog, self).__init__(parent)
self._console = LogMessageViewer(self)
layout = QtGui.QVBoxLayout()
layout.addWidget(self._console)
self.setLayout(layout)
XStream.stdout().messageWritten.connect(self._console.appendLogMessage)
XStream.stderr().messageWritten.connect(self._console.appendLogMessage)
if ( __name__ == '__main__' ):
app = QtGui.QApplication([])
dlg = MyDialog()
dlg.show()
app.exec_()
And:
Upvotes: 2
Reputation: 5546
I have made something similar in the past, a log message viewer that scrolls to the bottom when new messages are added. It is based on a QTextEdit
, but since the QTextBrowser
also has a verticalScrolBar
method I believe you can easily get it to work for the QTextBrower
.
class LogMessageViewer(QtWidgets.QTextEdit):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setReadOnly(True)
self.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
@pyqtSlot(str)
def appendLogMessage(self, msg):
horScrollBar = self.horizontalScrollBar()
verScrollBar = self.verticalScrollBar()
scrollIsAtEnd = verScrollBar.maximum() - verScrollBar.value() <= 10
self.append(msg)
if scrollIsAtEnd:
verScrollBar.setValue(verScrollBar.maximum()) # Scrolls to the bottom
horScrollBar.setValue(0) # scroll to the left
Note that it only scrolls automatically if the current scroll position is already within 10 pixels of the bottom. This allows you to scroll up and see earlier text without being interrupted by new output. Simply scroll back to the bottom to get live updates again.
Upvotes: 5