首页 > 解决方案 > PyQt5:如何让 QTextEdit 的内容在选项卡更改时刷新?

问题描述

[为清楚起见重新提出问题]

我有一个带有 2 个选项卡的简单 GUI,第一个包含用于程序日志的只读 QTextEdit,第二个包含用于用户输入的各种小部件和一个“生成”按钮。

GUI 是使用 Qt Designer 制作的。注意:除了只读的 QTextEdit 之外,所有元素都具有默认属性(它们未编辑/未更改选项)
箭头显示用于日志的 QTextEdit 和导致选项卡更改的生成按钮

在此处输入图像描述

单击生成按钮后,焦点选项卡将切换到第一个(带有日志),并且使用单独的线程来计算用户数据并将其日志写入 QTextEdit

class GUI:
    def __init__(self, core):
        self.core = core
        self.app = QtWidgets.QApplication(sys.argv)
        self.window = Application()
        self.__link_buttons__()

    def __link_buttons__(self):
        generate_button = self.window.get_generate_button()
        if generate_button is None:
            raise RuntimeError("could not find the generation button needed for the gui ( possibly an issue with the .gui file ), cannot start software")
        generate_button.clicked.connect(self.__generate_threaded)

    def __generate_threaded(self):
        logger.logger_instance.log("user requested generation", LogLevel.NORMAL)
        self.window.set_main_tab_to_log()
        if self.core.check_if_generating() is True:
        logger.logger_instance.log("Generator is currently in use", LogLevel.ERROR)
            return
        thread = threading.Thread(target=self.core.generate_gui)
        thread.start()

class Application(QtWidgets.QMainWindow):
    def __init__(self):
        super(Application, self).__init__()
        self.ui = uic.loadUi(settings.settings_instance.gui_path / "main_window.ui", self)
        self.show()
        self.__tabs = self.ui.findChild(QTabWidget, "mainTabs")
        self.__log_tab = self.ui.findChild(QWidget, "logTab")

    def get_gui_handle(self):
        return self.ui.findChild(QTextEdit, "LogOutput")

    def get_generate_button(self):
        return self.ui.findChild(QPushButton, "generateButton")

    def set_main_tab_to_log(self):
        self.__tabs.setCurrentWidget(self.__log_tab)

logger 是负责在 GUI 上写入 QTextEdit 的类,它调用get_gui_handle()start 上的方法来获取 QTextEdit 然后用于append()写入它(具有线程保护)

可能重要的细节:我使用的是标准 Python 线程 ( import threading),而不是 Qt,因为其他软件都使用它们,我不确定它们是否可以混合使用

记录器确实成功写入 QTextEdit,但应用程序未按预期显示文本。任何新文本都会正常显示,但以前的日志不会,并且只会在窗口调整大小/单击文本/更改选项卡/...时显示(我认为是让应用程序重新呈现 QTextEdit 的事件)

图片为清楚起见:

第一个日志按预期显示:

在此处输入图像描述

第二个日志也按预期显示,但第一个日志现在不可见

在此处输入图像描述

窗口调整大小/文本覆盖/选项卡更改后再次显示第一个日志

在此处输入图像描述

标签: python-3.xpyqt5

解决方案


问题是,即使我使用了线程保护,我还是从一个单独的线程调用该append()方法(线程保护还不够!)。在来自不同线程的调用后尝试刷新/实现 gui 也是一个坏主意。
这是因为 Qt 管理内部事件并且不使用主线程似乎绕过它们反过来导致 Qt 看不到它的内容已更改。

解决方案的重要说明:在这种特定情况下,使用线程或 qt 线程是无关紧要的,因为 qt 只是包装了一个线程库。然而,强烈建议在所有软件上使用 qt 线程,以避免在同一程序中使用 2 个不同的线程库,这反过来又会导致问题。

我使用pyqtSignal是为了能够与 gui 正确通信而不会出现问题

from PyQt5.QtCore import QObject, pyqtSignal

class GUI(QObject):
    __gui_log_signal = pyqtSignal(str)

    def __init___():
        # ...
        self.__gui_handle = self.window.get_gui_handle() # this method returns the reference to the QTextEdit directly
        self.__gui_log_signal.connect(self.__gui_handle.append)

这样我可以self.__gui_log_signal.emit("string or variable here")在需要时使用

需要注意的重要细节:(首先使获得答案比预期更难的事情)

  • pyqtSygnal必须在类的根目录中,而不是在else中__init__()它不能正常工作(在我的实例中emit()找不到方法)
  • pyqtSignal用于传递变量,它们只需要这样声明(例如:)pqtSygnal(str),然后连接到使用相同类型/变量计数的方法
  • 使用pyqtSygnal必须扩展QObject的类或扩展它的类

推荐阅读