mirror of
https://github.com/python/cpython.git
synced 2025-07-31 07:04:42 +00:00
Refined Qt GUI example in the logging cookbook. (GH-15045)
This commit is contained in:
parent
d04f8907ba
commit
472eced677
1 changed files with 39 additions and 17 deletions
|
@ -2760,8 +2760,8 @@ The following example shows how to log to a Qt GUI. This introduces a simple
|
||||||
``QtHandler`` class which takes a callable, which should be a slot in the main
|
``QtHandler`` class which takes a callable, which should be a slot in the main
|
||||||
thread that does GUI updates. A worker thread is also created to show how you
|
thread that does GUI updates. A worker thread is also created to show how you
|
||||||
can log to the GUI from both the UI itself (via a button for manual logging)
|
can log to the GUI from both the UI itself (via a button for manual logging)
|
||||||
as well as a worker thread doing work in the background (here, just random
|
as well as a worker thread doing work in the background (here, just logging
|
||||||
short delays).
|
messages at random levels with random short delays in between).
|
||||||
|
|
||||||
The worker thread is implemented using Qt's ``QThread`` class rather than the
|
The worker thread is implemented using Qt's ``QThread`` class rather than the
|
||||||
:mod:`threading` module, as there are circumstances where one has to use
|
:mod:`threading` module, as there are circumstances where one has to use
|
||||||
|
@ -2769,7 +2769,7 @@ The worker thread is implemented using Qt's ``QThread`` class rather than the
|
||||||
|
|
||||||
The code should work with recent releases of either ``PySide2`` or ``PyQt5``.
|
The code should work with recent releases of either ``PySide2`` or ``PyQt5``.
|
||||||
You should be able to adapt the approach to earlier versions of Qt. Please
|
You should be able to adapt the approach to earlier versions of Qt. Please
|
||||||
refer to the comments in the code for more detailed information.
|
refer to the comments in the code snippet for more detailed information.
|
||||||
|
|
||||||
.. code-block:: python3
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@ -2789,22 +2789,24 @@ refer to the comments in the code for more detailed information.
|
||||||
Signal = QtCore.pyqtSignal
|
Signal = QtCore.pyqtSignal
|
||||||
Slot = QtCore.pyqtSlot
|
Slot = QtCore.pyqtSlot
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Signals need to be contained in a QObject or subclass in order to be correctly
|
# Signals need to be contained in a QObject or subclass in order to be correctly
|
||||||
# initialized.
|
# initialized.
|
||||||
#
|
#
|
||||||
class Signaller(QtCore.QObject):
|
class Signaller(QtCore.QObject):
|
||||||
signal = Signal(str)
|
signal = Signal(str, logging.LogRecord)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Output to a Qt GUI is only supposed to happen on the main thread. So, this
|
# Output to a Qt GUI is only supposed to happen on the main thread. So, this
|
||||||
# handler is designed to take a slot function which is set up to run in the main
|
# handler is designed to take a slot function which is set up to run in the main
|
||||||
# thread. In this example, the function takes a single argument which is a
|
# thread. In this example, the function takes a string argument which is a
|
||||||
# formatted log message. You can attach a formatter instance which formats a
|
# formatted log message, and the log record which generated it. The formatted
|
||||||
# LogRecord however you like, or change the slot function to take some other
|
# string is just a convenience - you could format a string for output any way
|
||||||
# value derived from the LogRecord.
|
# you like in the slot function itself.
|
||||||
#
|
#
|
||||||
# You specify the slot function to do whatever GUI updates you want. The handler
|
# You specify the slot function to do whatever GUI updates you want. The handler
|
||||||
# doesn't know or care about specific UI elements.
|
# doesn't know or care about specific UI elements.
|
||||||
|
@ -2817,7 +2819,7 @@ refer to the comments in the code for more detailed information.
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
s = self.format(record)
|
s = self.format(record)
|
||||||
self.signaller.signal.emit(s)
|
self.signaller.signal.emit(s, record)
|
||||||
|
|
||||||
#
|
#
|
||||||
# This example uses QThreads, which means that the threads at the Python level
|
# This example uses QThreads, which means that the threads at the Python level
|
||||||
|
@ -2827,6 +2829,13 @@ refer to the comments in the code for more detailed information.
|
||||||
def ctname():
|
def ctname():
|
||||||
return QtCore.QThread.currentThread().objectName()
|
return QtCore.QThread.currentThread().objectName()
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Used to generate random levels for logging.
|
||||||
|
#
|
||||||
|
LEVELS = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
|
||||||
|
logging.CRITICAL)
|
||||||
|
|
||||||
#
|
#
|
||||||
# This worker class represents work that is done in a thread separate to the
|
# This worker class represents work that is done in a thread separate to the
|
||||||
# main thread. The way the thread is kicked off to do work is via a button press
|
# main thread. The way the thread is kicked off to do work is via a button press
|
||||||
|
@ -2851,7 +2860,8 @@ refer to the comments in the code for more detailed information.
|
||||||
while not QtCore.QThread.currentThread().isInterruptionRequested():
|
while not QtCore.QThread.currentThread().isInterruptionRequested():
|
||||||
delay = 0.5 + random.random() * 2
|
delay = 0.5 + random.random() * 2
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
logger.debug('Message after delay of %3.1f: %d', delay, i, extra=extra)
|
level = random.choice(LEVELS)
|
||||||
|
logger.log(level, 'Message after delay of %3.1f: %d', delay, i, extra=extra)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -2864,10 +2874,18 @@ refer to the comments in the code for more detailed information.
|
||||||
#
|
#
|
||||||
class Window(QtWidgets.QWidget):
|
class Window(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
COLORS = {
|
||||||
|
logging.DEBUG: 'black',
|
||||||
|
logging.INFO: 'blue',
|
||||||
|
logging.WARNING: 'orange',
|
||||||
|
logging.ERROR: 'red',
|
||||||
|
logging.CRITICAL: 'purple',
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
super(Window, self).__init__()
|
super(Window, self).__init__()
|
||||||
self.app = app
|
self.app = app
|
||||||
self.textedit = te = QtWidgets.QTextEdit(self)
|
self.textedit = te = QtWidgets.QPlainTextEdit(self)
|
||||||
# Set whatever the default monospace font is for the platform
|
# Set whatever the default monospace font is for the platform
|
||||||
f = QtGui.QFont('nosuchfont')
|
f = QtGui.QFont('nosuchfont')
|
||||||
f.setStyleHint(f.Monospace)
|
f.setStyleHint(f.Monospace)
|
||||||
|
@ -2880,7 +2898,7 @@ refer to the comments in the code for more detailed information.
|
||||||
self.handler = h = QtHandler(self.update_status)
|
self.handler = h = QtHandler(self.update_status)
|
||||||
# Remember to use qThreadName rather than threadName in the format string.
|
# Remember to use qThreadName rather than threadName in the format string.
|
||||||
fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s'
|
fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s'
|
||||||
formatter = logging.Formatter(f)
|
formatter = logging.Formatter(fs)
|
||||||
h.setFormatter(formatter)
|
h.setFormatter(formatter)
|
||||||
logger.addHandler(h)
|
logger.addHandler(h)
|
||||||
# Set up to terminate the QThread when we exit
|
# Set up to terminate the QThread when we exit
|
||||||
|
@ -2932,14 +2950,17 @@ refer to the comments in the code for more detailed information.
|
||||||
# that's where the slots are set up
|
# that's where the slots are set up
|
||||||
|
|
||||||
@Slot(str)
|
@Slot(str)
|
||||||
def update_status(self, status):
|
def update_status(self, status, record):
|
||||||
self.textedit.append(status)
|
color = self.COLORS.get(record.levelno, 'black')
|
||||||
|
s = '<pre><font color="%s">%s</font></pre>' % (color, status)
|
||||||
|
self.textedit.appendHtml(s)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def manual_update(self):
|
def manual_update(self):
|
||||||
levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
|
# This function uses the formatted message passed in, but also uses
|
||||||
logging.CRITICAL)
|
# information from the record to format the message in an appropriate
|
||||||
level = random.choice(levels)
|
# color according to its severity (level).
|
||||||
|
level = random.choice(LEVELS)
|
||||||
extra = {'qThreadName': ctname() }
|
extra = {'qThreadName': ctname() }
|
||||||
logger.log(level, 'Manually logged!', extra=extra)
|
logger.log(level, 'Manually logged!', extra=extra)
|
||||||
|
|
||||||
|
@ -2947,6 +2968,7 @@ refer to the comments in the code for more detailed information.
|
||||||
def clear_display(self):
|
def clear_display(self):
|
||||||
self.textedit.clear()
|
self.textedit.clear()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
QtCore.QThread.currentThread().setObjectName('MainThread')
|
QtCore.QThread.currentThread().setObjectName('MainThread')
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue