【问题标题】:PyQt5 : how can I get the content of a QTextEdit to refresh on tab change?PyQt5:如何让 QTextEdit 的内容在选项卡更改时刷新?
【发布时间】:2021-06-24 22:44:50
【问题描述】:

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

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

图形用户界面是使用 Qt 设计器制作的。注意:除了只读的 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)

记录器是GUI上负责写入QTextEdit的类,它在开始时调用get_gui_handle()方法获取QTextEdit然后使用append()写入它(有线程保护)

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

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

图片清晰:

第一个日志按预期显示:

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

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

【问题讨论】:

  • 除了上述关于 MRE 的建议之外,我要求您考虑这一点:您向我们提供了详细信息,但它们不是实际详细信息。例如:“我用于记录的 QTextEdit”:记录 what? 如何完成的? “输出是生成的”:生成如何?从什么?根据您要解释的内容,第一个想法是您可能正在尝试以某种方式使用线程(这 not 允许直接访问 UI 元素,因此存在更新问题 - 而您'很幸运,因为这通常会导致致命的崩溃),但在您的详细信息变为实际详细信息之前,我们无法确定。
  • 对不起,我会编辑问题,确实不够清楚
  • 添加了很多上下文和代码,谢谢指导^^
  • 现在好多了,但还是不够。正如所怀疑的那样,您正在使用线程,这肯定是问题的根源(用户界面应该始终在主线程中,并且绝对应该没有直接访问试图从外部线程获取 UI 元素),但我们无法提供更多信息,除非您提供 完整 minimal reproducible example:您的代码缺少 非常 重要部分,例如 @987654334 @ 以及实际尝试更新文本的内容;请注意,我们应该能够复制、粘贴和运行您的代码,而无需进行任何实质性修改。
  • 感谢您的回答。由于代码非常大,这有点难做,我认为如果我从头开始创建一个小示例而不是尝试提取重要部分(代码长达数千行)会更简单。 UI 从主线程运行,任何繁重的计算都在单独的线程上完成。您似乎对线程有所怀疑,我应该如何处理从单独线程到 QTextEdit 的写入?有没有我可以使用的事件/方法?

标签: python-3.x pyqt5


【解决方案1】:

问题在于,即使我使用了线程保护,我还是从一个单独的线程调用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 必须在类的根目录中,而不是在__init__() 中,否则它将无法正常工作(在我的实例中,找不到emit() 方法)
  • pyqtSignal 可用于传递变量,它们只需要这样声明(例如:pqtSygnal(str))然后连接到使用相同类型/变量计数的方法
  • 使用pyqtSygnal 的类必须扩展QObject 或扩展它的类

【讨论】:

    猜你喜欢
    • 2022-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-09
    • 2021-04-11
    • 1970-01-01
    相关资源
    最近更新 更多