В настоящее время я работаю над графическим интерфейсом, используя дизайнер qt. Мне интересно, как мне печатать строки в графическом интерфейсе, который действует как окно регистратора. Я использую pyqt5.
Лучший способ отображать журналы в pyqt?
Ответы (6)
Адаптировано из примера Todd Vanyo для PyQt5:
import sys
from PyQt5 import QtWidgets
import logging
# Uncomment below for terminal log messages
# logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')
class QTextEditLogger(logging.Handler):
def __init__(self, parent):
super().__init__()
self.widget = QtWidgets.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
class MyDialog(QtWidgets.QDialog, QtWidgets.QPlainTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
logTextBox = QTextEditLogger(self)
# You can format what is printed to text box
logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(logTextBox)
# You can control the logging level
logging.getLogger().setLevel(logging.DEBUG)
self._button = QtWidgets.QPushButton(self)
self._button.setText('Test Me')
layout = QtWidgets.QVBoxLayout()
# Add the new logging box widget to the layout
layout.addWidget(logTextBox.widget)
layout.addWidget(self._button)
self.setLayout(layout)
# Connect signal to slot
self._button.clicked.connect(self.test)
def test(self):
logging.debug('damn, a bug')
logging.info('something to remember')
logging.warning('that\'s not right')
logging.error('foobar')
app = QtWidgets.QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
dlg.raise_()
sys.exit(app.exec_())
appendPlainText
должен быть подключен к сигналу, а не вызывать его.
- person tobilocker; 22.01.2020
AttributeError: module 'logging' has no attribute 'Handler'
- person the_economist; 05.11.2020
Make sure 'QTextCursor' is registered using qRegisterMetaType()
при регистрации через обратные вызовы. Поточно-безопасная версия работала у меня довольно хорошо. stackoverflow.com/a/60528393/3598205
- person sa_penguin; 20.07.2021
Если вы используете модуль Python logging
, вы можете легко создать собственный обработчик ведения журнала, который передает сообщения журнала в экземпляр QPlainTextEdit
(как описано Кристофером).
Для этого вы сначала подкласс logging.Handler
. В этом __init__
мы создаем QPlainTextEdit
, который будет содержать журналы. Ключевым моментом здесь является то, что дескриптор будет получать сообщения через функцию emit()
. Поэтому мы перегружаем эту функцию и передаем текст сообщения в файл QPlainTextEdit
.
import logging
class QPlainTextEditLogger(logging.Handler):
def __init__(self, parent):
super(QPlainTextEditLogger, self).__init__()
self.widget = QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
def write(self, m):
pass
Создайте объект из этого класса, передав ему родителя для QPlainTextEdit
(например, главное окно или макет). Затем вы можете добавить этот обработчик для текущего регистратора.
# Set up logging to use your widget as a handler
log_handler = QPlainTextEditLogger(<parent widget>)
logging.getLogger().addHandler(log_handler)
Вот полный рабочий пример, основанный на ответе mfitzp:
import sys
from PyQt4 import QtCore, QtGui
import logging
# Uncomment below for terminal log messages
# logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')
class QPlainTextEditLogger(logging.Handler):
def __init__(self, parent):
super().__init__()
self.widget = QtGui.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
class MyDialog(QtGui.QDialog, QPlainTextEditLogger):
def __init__(self, parent=None):
super().__init__(parent)
logTextBox = QPlainTextEditLogger(self)
# You can format what is printed to text box
logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(logTextBox)
# You can control the logging level
logging.getLogger().setLevel(logging.DEBUG)
self._button = QtGui.QPushButton(self)
self._button.setText('Test Me')
layout = QtGui.QVBoxLayout()
# Add the new logging box widget to the layout
layout.addWidget(logTextBox.widget)
layout.addWidget(self._button)
self.setLayout(layout)
# Connect signal to slot
self._button.clicked.connect(self.test)
def test(self):
logging.debug('damn, a bug')
logging.info('something to remember')
logging.warning('that\'s not right')
logging.error('foobar')
if (__name__ == '__main__'):
app = None
if (not QtGui.QApplication.instance()):
app = QtGui.QApplication([])
dlg = MyDialog()
dlg.show()
dlg.raise_()
if (app):
app.exec_()
QPlainTextEditLogger
в MyDialog
? Я пытаюсь преобразовать этот пример в PyQt5 и не смог заставить его работать, не удалив это второе наследование. Вроде и без него нормально работает.
- person Filip S.; 26.01.2018
Поточно-безопасная версия
class QTextEditLogger(logging.Handler, QtCore.QObject):
appendPlainText = QtCore.pyqtSignal(str)
def __init__(self, parent):
super().__init__()
QtCore.QObject.__init__(self)
self.widget = QtWidgets.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
self.appendPlainText.connect(self.widget.appendPlainText)
def emit(self, record):
msg = self.format(record)
self.appendPlainText.emit(msg)
Применение
logTextBox = QTextEditLogger(self)
# log to text box
logTextBox.setFormatter(
logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
logging.getLogger().addHandler(logTextBox)
logging.getLogger().setLevel(logging.DEBUG)
# log to file
fh = logging.FileHandler('my-log.log')
fh.setLevel(logging.DEBUG)
fh.setFormatter(
logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
logging.getLogger().addHandler(fh)
Ответ Алекса должен быть в порядке в сценарии с одним потоком, но если вы входите в другой поток (QThread), вы можете получить следующее предупреждение:
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Это связано с тем, что вы изменяете GUI (self.widget.appendPlainText(msg)
) из потока, отличного от основного потока, без использования механизма Qt Signal/Slot.
Вот мое решение:
# my_logger.py
import logging
from PyQt5.QtCore import pyqtSignal, QObject
class Handler(QObject, logging.Handler):
new_record = pyqtSignal(object)
def __init__(self, parent):
super().__init__(parent)
super(logging.Handler).__init__()
formatter = Formatter('%(asctime)s|%(levelname)s|%(message)s|', '%d/%m/%Y %H:%M:%S')
self.setFormatter(formatter)
def emit(self, record):
msg = self.format(record)
self.new_record.emit(msg) # <---- emit signal here
class Formatter(logging.Formatter):
def formatException(self, ei):
result = super(Formatter, self).formatException(ei)
return result
def format(self, record):
s = super(Formatter, self).format(record)
if record.exc_text:
s = s.replace('\n', '')
return s
# gui.py
... # GUI code
...
def setup_logger(self)
handler = Handler(self)
log_text_box = QPlainTextEdit(self)
self.main_layout.addWidget(log_text_box)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
handler.new_record.connect(log_text_box.appendPlainText) # <---- connect QPlainTextEdit.appendPlainText slot
...
Похоже, вы захотите использовать виджет QPlainTextEdit, настроенный только для чтения .
Попробуйте изменить цвет фона на серый, чтобы дать пользователю понять, что его нельзя редактировать. Это также зависит от вас, хотите ли вы, чтобы его можно было прокручивать или выделять текст.
Этот ответ может помочь вам начать создавать подклассы QPlainTextEdit для прокрутки с выводом, сохранения в файл, что угодно.