PyQt5 Unexpected Error "QObject::connect:" при использовании отдельного QThread
При использовании отдельного потока для синхронизации с БД в PyQt5 появляется пара ошибок:
QObject::connect: Cannot queue arguments of type 'QTextCharFormat'
(Make sure 'QTextCharFormat' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Долго пытался понять откуда берется сообщения и вычислил виновника - HistoryBox() текстовое поле для вывода результатов (логов), но в чем именно причина не понимаю. Подскажите что за ошибка и как пофиксить? (для воспроизведения события код максимально упростил)
main.py
import logging, sys
from PyQt5.QtCore import pyqtSignal, QObject, Qt, QThread
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from history import HistoryBox
logger = logging.getLogger("Test")
class AppsSyncWorker(QObject):
sync_progress_changed_signal = pyqtSignal(int)
sync_progress_result_signal = pyqtSignal(bool)
def __init__(self, config_section: str = None, config_id: int = None, parent=None):
super(AppsSyncWorker, self).__init__(parent)
self.__config_section = config_section
self.__config_id = config_id
def task_sync_run(self):
logging.info("Sync Run")
progress_counter = 0
progress_result = []
for item in range(10):
progress_counter += 1
progress_result.append(True)
self.sync_progress_changed_signal.emit(progress_counter)
QThread.msleep(100)
self.sync_progress_result_signal.emit(all(progress_result))
logging.info("Sync Done")
class MainInterface(QMainWindow):
def __init__(self):
super().__init__()
self.sync_thread = None
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
# Flexible Window with Mim Size Limitation
self.setMinimumHeight(350)
self.setMinimumWidth(550)
self.run_button = QPushButton("Run")
self.run_button.clicked.connect(self.__sync_worker_start)
self.sync_progressbar = QProgressBar()
self.sync_progressbar.setMaximum(10)
g_layout = QGridLayout(self.centralWidget)
g_layout.addWidget(self.run_button, 1, 1)
g_layout.addWidget(self.sync_progressbar, 1, 2)
g_layout.addWidget(HistoryBox(), 2, 1, 1, 2)
def __sync_worker_start(self):
if not self.sync_thread:
self.run_button.setDisabled(True)
self.sync_thread = QThread()
self.worker = AppsSyncWorker()
self.worker.moveToThread(self.sync_thread)
self.sync_thread.started.connect(self.worker.task_sync_run)
self.sync_thread.start()
self.worker.sync_progress_changed_signal.connect(self.sync_progressbar.setValue, Qt.QueuedConnection)
self.worker.sync_progress_result_signal.connect(self.__sync_worker_end)
def __sync_worker_end(self, sync_result: bool):
self.sync_thread.terminate()
self.sync_thread.wait(500)
self.sync_progressbar.setFormat(
"Synchronization {result}".format(result="Success" if sync_result else "Error Occurred"))
if self.sync_thread.isFinished():
self.run_button.setDisabled(False)
self.sync_thread = None
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle("Fusion")
ex = MainInterface()
ex.show()
sys.exit(app.exec_())
history.py
import logging
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from log_settings import MAIN_LOG_FORMAT
class HistoryBox(QWidget):
"""
Main Tabs - History Tab Interface Class
"""
def __init__(self, parent=None):
super(History, self).__init__(parent)
history_handler = HistoryQTextEditHandler(self)
history_handler.setFormatter(logging.Formatter(MAIN_LOG_FORMAT))
# TODO: Change Log Level Management
history_handler.setLevel(logging.INFO)
logging.getLogger().addHandler(history_handler)
history_text = history_handler.history_text
history_box = QGroupBox("History")
history_box_h_layout = QHBoxLayout(history_box)
history_box_h_layout.addWidget(history_text)
tab_v_layout = QVBoxLayout(self)
tab_v_layout.addWidget(history_box)
class HistoryQTextEditHandler(logging.Handler):
"""
Main Tabs - History QTextEdit Logging Handler
"""
def __init__(self, parent):
super().__init__()
self.history_text = QTextEdit(parent)
self.history_text.setReadOnly(True)
self.history_text.setLineWrapMode(QTextEdit.NoWrap)
def emit(self, record: logging.LogRecord):
"""
Emit Log Signal
:param record:
:return:
Other way to set the text color
log_message = "<span style='color:#ff00ff;'>{log_message}</span>"
"""
cursor = QTextCursor(self.history_text.document())
cursor.setPosition(0)
self.history_text.setTextCursor(cursor)
log_message = self.format(record)
if "DEBUG" in log_message:
self.history_text.setTextColor(QColor("#ff00ff")) # magenta
elif "INFO" in log_message:
self.history_text.setTextColor(QColor("#00ffff")) # cyan
elif "WARNING" in log_message:
self.history_text.setTextColor(QColor("#ffff00")) # yellow
else:
self.history_text.setTextColor(QColor("#ff0000")) # red
self.history_text.insertPlainText(log_message + "\n")
log_settings.py
import logging
MAIN_LOG_FORMAT = "[%(asctime)s]:[%(filename)s:%(lineno)d] - %(levelname)s - %(name)s - %(message)s"
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter(MAIN_LOG_FORMAT))
stream_handler.setLevel(logging.INFO)
logger.addHandler(stream_handler)