PyQt5:如何在 QThread 和某些子类之间“通信”?

Posted

技术标签:

【中文标题】PyQt5:如何在 QThread 和某些子类之间“通信”?【英文标题】:PyQt5: How to 'communicate' between QThread and some sub-class? 【发布时间】:2018-01-22 04:00:08 【问题描述】:

对于这个问题,我指的是@eyllanesc 在PyQt5: How to scroll text in QTextEdit automatically (animational effect)? 中的回答

@eyllanesc 展示了如何使用verticalScrollBar() 使文本自动平滑滚动。效果很好。

对于这个问题,我添加了一些额外的行,以使用QThread 来获取文本。

我想在这里实现的目标:QThread classAnimationTextEdit class '通信',这样滚动时间可以由文本长度确定。这样程序就会在滚动过程结束时停止。

我必须说这对我来说是非常非常棘手的任务。我想先展示一下程序流程,和我想象的一样。

更新:我的代码如下。它有效,但是...

代码问题:当文本停止滚动时,time.sleep() 仍然有效。该应用程序在那里等到time.sleep() 停止。

我想得到什么:当文本停止滚动时,time.sleep() 运行到它的结束值。

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
import time
import sqlite3


class AnimationTextEdit(QTextEdit):
    # signal_HowLongIsTheText = pyqtSignal(int)  # signal to tell the QThread, how long the text is

    def __init__(self, *args, **kwargs):
        QTextEdit.__init__(self, *args, **kwargs)
        self.animation = QVariantAnimation(self)
        self.animation.valueChanged.connect(self.moveToLine)

    # def sent_Info_to_Thread(self):
    #     self.obj_Thread = Worker()
    #     self.signal_HowLongIsTheText.connect(self.obj_Thread.getText_HowLongIsIt)
    #     self.signal_HowLongIsTheText.emit(self.textLength)
    #     self.signal_HowLongIsTheText.disconnect(self.obj_Thread.getText_HowLongIsIt)


    @pyqtSlot()
    def startAnimation(self):
        self.animation.stop()
        self.animation.setStartValue(0)

        self.textLength = self.verticalScrollBar().maximum()
        # self.sent_Info_to_Thread()


        self.animation.setEndValue(self.textLength)
        self.animation.setDuration(self.animation.endValue()*4)
        self.animation.start()

    @pyqtSlot(QVariant)
    def moveToLine(self, i):
        self.verticalScrollBar().setValue(i)



class Worker(QObject):
    finished = pyqtSignal()
    textSignal = pyqtSignal(str)

    # @pyqtSlot(int)
    # def getText_HowLongIsIt(self, textLength):
    #     self.textLength = textLength

    @pyqtSlot()
    def getText(self):
        longText = "\n".join([": long text - auto scrolling ".format(i) for i in range(100)])

        self.textSignal.emit(longText)

        time.sleep(10)
        # time.sleep(int(self.textLength / 100))
        # My question is about the above line: time.sleep(self.textLength)
        # Instead of giving a fixed sleep time value here,
        # I want let the Worker Class know,
        # how long it will take to scroll all the text to the end.

        self.finished.emit()


class MyApp(QWidget):
    def __init__(self):
        super(MyApp, self).__init__()
        self.setFixedSize(600, 400)
        self.initUI()
        self.startThread()

    def initUI(self):
        self.txt = AnimationTextEdit(self)
        self.btn = QPushButton("Start", self)

        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.txt)
        self.layout.addWidget(self.btn)

        self.btn.clicked.connect(self.txt.startAnimation)

    def startThread(self):
        self.obj = Worker()
        self.thread = QThread()

        self.obj.textSignal.connect(self.textUpdate)
        self.obj.moveToThread(self.thread)
        self.obj.finished.connect(self.thread.quit)
        self.thread.started.connect(self.obj.getText)
        self.thread.finished.connect(app.exit)
        self.thread.start()

    def textUpdate(self, longText):
        self.txt.append(longText)
        self.txt.moveToLine(0)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

感谢您的帮助和提示。我做错了什么?

【问题讨论】:

我有点困惑。睡眠似乎只是为了延迟 finished 信号,该信号似乎最终被设置为退出应用程序。你到底想在这里发生什么?最好的解决方案是构建应用程序,这样您就不需要在线程中休眠,但如果不知道休眠的目的,我无法建议最佳选择。 @three_pineapples 谢谢。我更新了我的问题。现在代码可以工作了。如果您运行代码,您会看到文本何时停止滚动,time.sleep() 仍在工作,直到 App 停止。让滚动和 time.sleep() 同时停止,这是我的问题。 @LucasMeier time.sleep() 运行到它的结束值是什么意思。? time.sleep() 根本不运行,它只是在你指定的时间阻塞下一行代码。 @LucasMeier 为什么要让 Worker 知道持续时间?尽管我使用 setDuration() 将时间放在动画上,但由于各种原因,它可能会有所不同,因此并不准确。 【参考方案1】:

虽然在动画中确定了持续时间,但有必要了解这并不准确,这可能会因多种原因而有所不同,因此计算使用睡眠以等待它在特定时间结束并关闭应用程序可能失败。

如果您的主要目标是当动画完成时程序执行完成,那么您必须使用 finished QVariantAnimation 信号来完成线程的执行,该信号在执行完成时发出。

class AnimationTextEdit(QTextEdit):
    def __init__(self, *args, **kwargs):
        QTextEdit.__init__(self, *args, **kwargs)
        self.animation = QVariantAnimation(self)
        self.animation.valueChanged.connect(self.moveToLine)

    @pyqtSlot()
    def startAnimation(self):
        self.animation.stop()
        self.animation.setStartValue(0)
        self.textLength = self.verticalScrollBar().maximum()
        self.animation.setEndValue(self.textLength)
        self.animation.setDuration(self.animation.endValue()*4)
        self.animation.start()

    @pyqtSlot(QVariant)
    def moveToLine(self, i):
        self.verticalScrollBar().setValue(i)


class Worker(QObject):
    textSignal = pyqtSignal(str)
    @pyqtSlot()
    def getText(self):
        longText = "\n".join([": long text - auto scrolling ".format(i) for i in range(100)])
        self.textSignal.emit(longText)


class MyApp(QWidget):
    def __init__(self):
        super(MyApp, self).__init__()
        self.setFixedSize(600, 400)
        self.initUI()
        self.startThread()

    def initUI(self):
        self.txt = AnimationTextEdit(self)
        self.btn = QPushButton("Start", self)

        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.txt)
        self.layout.addWidget(self.btn)

        self.btn.clicked.connect(self.txt.startAnimation)

    def startThread(self):
        self.obj = Worker()
        self.thread = QThread()

        self.obj.textSignal.connect(self.textUpdate)
        self.obj.moveToThread(self.thread)
        self.txt.animation.finished.connect(self.thread.quit)
        self.thread.started.connect(self.obj.getText)
        self.thread.finished.connect(app.exit)
        self.thread.start()

    def textUpdate(self, longText):
        self.txt.append(longText)
        self.txt.moveToLine(0)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

【讨论】:

太棒了!非常感谢!

以上是关于PyQt5:如何在 QThread 和某些子类之间“通信”?的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5 - 如何在使用 QThread 时减少 CPU 使用率(低于 50%)?

PyQt5:使用 QObject 和 QThread 时出现 AttributeError

PyQt5.QThread 的 start() 方法不执行 run() 方法

PyQt5 QThread 不会因终止或标志变量而停止

在 PyQt5 中取消 QThread

PyQt5 与 QThread 崩溃