Python毫不拖延地记录到PySide小部件

时间:2017-04-24 19:11:27

标签: python qt logging pyqt pyside

问题:我有一个 PySide 应用程序,它已经使用日志记录控制台输出,但其日志记录应该以 LogRecords 的方式进行扩展也会像QTextBrowser一样在小部件中显示立即。我知道这通常是通过一个工作线程来完成的,该工作线程指示main / gui线程中的一个插槽,但是由于代码库相当大,并且日志记录可能用于一些阻塞核心如果没有更大的重构,无论如何都可以实现GUI中的即时反馈,那将是很好的。

示例:以下是一些示例代码示例。它显示:

  • 一个logger,有两个处理程序:
    1. StreamHandler登录到控制台
    2. QSignalHandler发出信号,其中的消息已连接到将消息附加到QTextBrowser的广告位。
  • 一种方法long_running_core_operation_that_should_log_immediately_to_ui(),用于模拟阻塞核心操作的日志记录。
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import logging
import sys

from PySide import QtCore
from PySide import QtGui


class QSignaler(QtCore.QObject):
    log_message = QtCore.Signal(unicode)


class SignalHandler(logging.Handler):
    """Logging handler to emit QtSignal with log record text."""

    def __init__(self, *args, **kwargs):
        super(SignalHandler, self).__init__(*args, **kwargs)
        self.emitter = QSignaler()

    def emit(self, logRecord):
        msg = "{0}".format(logRecord.getMessage())
        self.emitter.log_message.emit(msg)
        # When the line below is enabled, logging is immediate/otherwise events
        # on the queue will be processed when the slot has finished.
        # QtGui.qApp.processEvents()


# configure logging
logging.basicConfig(level=logging.DEBUG)  # adds StreamHandler

signal_handler = SignalHandler()

logger = logging.getLogger()
logger.addHandler(signal_handler)


class TestWidget(QtGui.QWidget):
    def __init__(self, *args, **kwargs):
        super(TestWidget, self).__init__(*args, **kwargs)

        layout = QtGui.QVBoxLayout(self)

        # text_browser
        self.text_browser = QtGui.QTextBrowser()
        layout.addWidget(self.text_browser)

        # btn_start_operation
        self.btn_start_operation = QtGui.QPushButton("Start operation")
        self.btn_start_operation.clicked.connect(
            self.long_running_core_operation_that_should_log_immediately_to_ui)
        layout.addWidget(self.btn_start_operation)

        # btn_clear
        self.btn_clear = QtGui.QPushButton("Clear")
        self.btn_clear.clicked.connect(self.text_browser.clear)
        layout.addWidget(self.btn_clear)

    def long_running_core_operation_that_should_log_immediately_to_ui(self):
        for index in range(10000):
            msg = "{0}".format(index)
            logger.info(msg)


# test
if (__name__ == "__main__"):
    app = QtGui.QApplication(sys.argv)
    test_widget = TestWidget()
    signal_handler.emitter.log_message.connect(test_widget.text_browser.append)
    test_widget.show()
    sys.exit(app.exec_())

问题:StreamHandler日志记录到stdout时,{em> PySide 事件循环会发生QSignalHandler日志记录再次处理事件,这发生在for循环之后。

  • 是否有推荐的方法,在不调用核心操作的工作线程的情况下从QSignalHandler实现立即日志记录?
  • 安全/建议在QtGui.qApp.processEvents()发出记录信号后调用QSignalHandler吗? (当取消注释时,直接登录到GUI)
  • 在阅读documentation信号连接类型时,Qt.DirectConnection: The slot is invoked immediately, when the signal is emitted.我会认为QSignalHandler应该像StreamHandler一样立即更新,不应该吗?

1 个答案:

答案 0 :(得分:3)

  

是否有推荐的方法,即可在不调用核心操作的工作线程的情况下立即从QSignalHandler进行日志记录?

我不知道触发重绘日志小部件的任何其他方式,而不是处理事件。

请注意,在日志窗口小部件上调用repaint()会产生误导并且没有所需的效果,它只会强制调用日志窗口小部件的paintEvent()方法。 repaint()没有做关键的事情,比如将窗口表面复制到窗口系统。

  

在QSignalHandler发出记录信号后,是否安全/建议只调用QtGui.qApp.processEvents()? (取消注释时,直接登录到GUI)。

建议使用单独的线程或异步操作。如果您不能像在您的情况下那样调用processEvents(),则建议使用QProgressDialog::setValue()。甚至Qt也会在processEvents()内将其用于相同目的。

通常,手动处理事件可能很危险,应谨慎处理。调用QTextBrowser::append()后,完整的应用程序状态可能会有所不同。例如,日志窗口小部件可能不再存在,因为用户关闭了窗口!在您的示例代码中没有问题,因为信号/插槽连接将自动断开连接,但想象一下,如果您因为关闭而被删除后尝试访问日志窗口小部件 - 您可能会遇到崩溃。所以要小心。

  

在阅读信号连接类型的文档时,它说Qt.DirectConnection:当发出信号时,立即调用插槽。我会认为QSignalHandler应该像StreamHandler一样立即更新,不应该吗?

您的案例QTextBrowser::append() 中的广告位立即被调用。但是,processEvents()不会立即重新绘制。相反,它会安排重新绘制(通过QWidget::update()),并在Qt处理事件时进行实际重新绘制。这是当您返回事件循环或手动调用DirectConnection时。 因此,在发出信号时确实会立即调用插槽,至少在使用默认 $send_mail = mail($to_email, $subject, $message_body, $headers); if(!$send_mail) { //If mail couldn't be sent output error. Check your PHP email configuration (if it ever happens) $output = json_encode(array('type'=>'message', 'text' => 'Hi '.$user_name .' Achtung, es gab einen Fehler bei der Übertragung !')); die($output); }else{ $output = json_encode(array('type'=>'message', 'text' => 'Hi '.$user_name .' Vielen Dank für Ihre Anfrage. Wir prüfen diese umgehend und geben Ihnen schnellstmöglich eine Antwort')); die($output); } } 时。然而重新粉刷不会立即发生。