Python access widgets of parent class in Qthread

2020-04-20 05:47发布

问题:

I want to access the parent class widgets in the QThread class

This line gives hangs GUI "Example().setWindowTitle("Window")"

How can I do that?

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.myclass2 = myclass2()
        self.myclass2.start()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 300, 220)
        self.setWindowTitle('Icon')
        self.setWindowIcon(QIcon('web.png'))
        self.show()


class myclass2(QThread):
    def __init__(self, parent=None):
        super(myclass2, self).__init__(parent)

    def run(self):
        while True:
            time.sleep(.1)
            print(" in thread \n")
            Example().setWindowTitle("Window")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

回答1:

You must understand the following expression:

Example().setWindowTitle("Window")

it is equivalent to:

obj = Example()
obj.setWindowTitle("Window")

That is, you are creating another Example object other than ex = Example(), and that object is creating another myclass2 object, and that other myclass2 object is creating another Example, and an infinite loop is clearly being created.


Another thing that in the future could cause you problems is to establish the same name for different things, although in this case it is not a problem but in future occasions it could bring you problems, the code to which I refer is:

self.myclass2 = myclass2()

For example, it is recommended that classes should start with capital letters.


Another error that is valid only in Qt is that the GUI can not be created or manipulated in a thread other than the main thread. So you can not change the title directly in the other thread but there are 2 methods:

1. QMetaObject::invokeMethod(...)

But for this we must pass the GUI through a property:

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.myclass2 = MyClass()
        self.myclass2.gui = self
        self.myclass2.start()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 300, 220)
        self.setWindowTitle('Icon')
        self.setWindowIcon(QIcon('web.png'))
        self.show()


class MyClass(QThread):
    def run(self):
        while True:
            time.sleep(.1)
            print(" in thread \n")
            QMetaObject.invokeMethod(self.gui, "setWindowTitle", Qt.QueuedConnection, Q_ARG(str, "Window"))

2. signals & slots

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.myclass2 = MyClass()
        self.myclass2.titleChanged.connect(self.setWindowTitle)
        self.myclass2.start()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 300, 220)
        self.setWindowTitle('Icon')
        self.setWindowIcon(QIcon('web.png'))
        self.show()


class MyClass(QThread):
    titleChanged = pyqtSignal(str)

    def run(self):
        while True:
            time.sleep(.1)
            print(" in thread \n")
            self.titleChanged.emit("Window")

PLUS:

You should not modify the GUI from the thread directly but send the data through a signal:

import sys

from PyQt5 import QtCore, QtGui, QtWidgets

class Example(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        lay = QtWidgets.QVBoxLayout(self)
        le = QtWidgets.QLineEdit()
        lay.addWidget(le)
        self.myclass2 = MyClass()
        self.myclass2.titleChanged.connect(self.setWindowTitle)
        self.myclass2.infoChanged.connect(le.setText) # <-- connect signal
        self.myclass2.start()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 300, 220)
        self.setWindowTitle('Icon')
        self.setWindowIcon(QtGui.QIcon('web.png'))
        self.show()


class MyClass(QtCore.QThread):
    titleChanged = QtCore.pyqtSignal(str)
    infoChanged = QtCore.pyqtSignal(str) # <-- create signal

    def run(self):
        counter = 0
        while True:
            QtCore.QThread.msleep(100)
            print(" in thread \n")
            self.titleChanged.emit("Window")
            self.infoChanged.emit("{}".format(counter)) # <-- emit signal
            counter += 1


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())