PyQt4 中的 QThreading PyQtGraph PlotWidgets

Posted

技术标签:

【中文标题】PyQt4 中的 QThreading PyQtGraph PlotWidgets【英文标题】:QThreading PyQtGraph PlotWidgets in PyQt4 【发布时间】:2017-06-08 02:23:30 【问题描述】:

作为我几天前解决的问题的延续here,我有一个 PyQt4 GUI,它嵌入了两个 PyQtGraph 的 PlotWidgets,每个都从一个按钮触发的线程随机附加数组中设置/更新数据。它运行良好,并且 GUI 响应迅速。但是有一点,图表停止显示它们各自的 PlotWidgets 内的更新,我必须最小化/最大化才能看到在 PlotWidgets 内绘制的更新。代码如下:

import random
import sys

import pyqtgraph as pg
import time
from PyQt4 import QtGui, QtCore


class MyThread(QtCore.QThread):
    def __init__(self, curve, parent=None):
        super(MyThread, self).__init__(parent=parent)
        self.curve = curve
        self.data = [0]
        app.processEvents()#increases the time the drawings are shown

    def run(self):
        app.processEvents() #increases the time the drawings are shown
        while True:
            self.data.append(self.data[-1] + 0.2 * (0.5 - random.random()))
            self.curve.setData(self.data, downsample=10) #crashes without
            time.sleep(0.1)


class LoginWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        super(LoginWidget, self).__init__(parent)
        layout = QtGui.QHBoxLayout()
        self.button = QtGui.QPushButton('Start Plotting')
        layout.addWidget(self.button)
        self.plot = pg.PlotWidget()
        layout.addWidget(self.plot)
        self.setLayout(layout)
        self.button.clicked.connect(self.plotter)
        app.processEvents()

    def plotter(self):
        self.curve = self.plot.getPlotItem().plot()
        myThread = MyThread(self.curve, self)
        myThread.start()


class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.centralwidget = QtGui.QWidget(self)
        self.setCentralWidget(self.centralwidget)
        self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
        self.login_widget_1 = LoginWidget(self)
        self.horizontalLayout.addWidget(self.login_widget_1)
        self.login_widget_2 = LoginWidget(self)
        self.horizontalLayout.addWidget(self.login_widget_2)
        self.setCentralWidget(self.centralwidget)


if __name__ == '__main__':
    global app
    app = QtGui.QApplication(sys.argv)
    w = MainWindow()
    w.show()    
    sys.exit(app.exec_())

任何关于我不必最大化/最小化以在 GUI 中获取更新的方法的任何线索或解决方法都会让我非常高兴。

根据@ImportanceOfBeingErnest 在以下答案中的建议,我尝试按照Python Wiki 将GUI 和线程与信号和插槽连接为:

import random
import sys
import pyqtgraph as pg
import time
from PyQt4 import QtGui, QtCore


class MyThread(QtCore.QThread):
    def __init__(self, parent=None):
        super(MyThread, self).__init__(parent=parent)
        self.data = [0]

    def run(self):
        while True:
            self.data.append(self.data[-1] + 0.2 * (0.5 - random.random()))
            self.emit(SIGNAL("output(data)"), self.data)
            time.sleep(0.1)


class LoginWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        super(LoginWidget, self).__init__(parent)
        self.myThread = MyThread()
        layout = QtGui.QHBoxLayout()
        self.button = QtGui.QPushButton('Start Plotting')
        layout.addWidget(self.button)
        self.plot = pg.PlotWidget()
        layout.addWidget(self.plot)
        self.setLayout(layout)
        self.button.clicked.connect(self.plotter)
        self.connect(self.myThread, SIGNAL("output(data)"), self.plotter)

    def plotter(self, data):
        self.curve = self.plot.getPlotItem().plot()
        self.curve.setData(data, downsample=10)


class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.centralwidget = QtGui.QWidget(self)
        self.setCentralWidget(self.centralwidget)
        self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
        self.login_widget_1 = LoginWidget(self)
        self.horizontalLayout.addWidget(self.login_widget_1)
        self.login_widget_2 = LoginWidget(self)
        self.horizontalLayout.addWidget(self.login_widget_2)
        self.setCentralWidget(self.centralwidget)


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

但这给了我错误:

"in self.connect(self.myThread, SIGNAL("output(data)"), self.plotter)
NameError: name 'SIGNAL' is not defined"

通过阅读question from 4 years ago,我用 QtCore.SIGNAL 替换 SIGNAL 得到以下错误:

"in self.connect(self.myThread, QtCore.SIGNAL("output(data)"), self.plotter)
TypeError: C++ type 'data' is not supported as a slot argument type"

然后将 TypeError 忽略为:

    try:
    self.connect(self.myThread, QtCore.SIGNAL("output(data)"), self.plotter)
except TypeError:
    pass

GUI 启动,但是当我最终单击按钮并连接到 self.plotter 函数时,我收到以下错误:

"in plotter
self.curve.setData(data, downsample=10)
File "C:\Users\iyv\AppData\Local\Programs\Python\Python34_64\lib\site-
packages\pyqtgraph\graphicsItems\PlotDataItem.py", line 381, in setData
raise Exception('Invalid data type %s' % type(data))
Exception: Invalid data type <class 'bool'>"

我很困惑。我应该联系 PyQtGraph 吗?欢迎任何帮助。

【问题讨论】:

我在下面编辑了我的答案以显示一个工作示例。 【参考方案1】:

您需要将 GUI 和线程分开。在这种情况下,它们不得共享任何 GUI 对象,例如绘图小部件!

我不确定您是否需要线程,因为您的数据不是很大并且每秒仅更新 10 次。但是如果你决定使用它,只使用signals and slots 来在线程和GUI 之间进行通信。


下面是您的程序的修改版本,它使用线程并且工作正常。它使用新样式signals and slots。希望这更容易理解,因为您可以使用 object 作为数据类型。

在你的程序中有两件事没有意义:

您在绘图槽中重新定义了 self.curve。 按下按钮时调用绘图槽。相反,按下按钮应该会启动线程。

我已经更正了。

import random
import sys
import pyqtgraph as pg
import time
from PyQt4 import QtGui, QtCore


class MyThread(QtCore.QThread):
    signal = QtCore.pyqtSignal(object)
    def __init__(self, parent=None):
        super(MyThread, self).__init__(parent=parent)
        self.data = [0]

    def __del__(self):
        self.exiting = True
        self.wait()

    def run(self):
        while True:
            self.data.append(random.random())
            self.signal.emit(self.data)
            time.sleep(0.1)


class LoginWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        super(LoginWidget, self).__init__(parent)
        self.myThread = MyThread()
        layout = QtGui.QHBoxLayout()
        self.button = QtGui.QPushButton('Start Plotting')
        layout.addWidget(self.button)
        self.plot = pg.PlotWidget()
        layout.addWidget(self.plot)
        self.setLayout(layout)
        self.curve = self.plot.getPlotItem().plot()
        self.button.clicked.connect(self.start)


    def plotter(self, data):
        self.curve.setData(data)

    def start(self):
        self.myThread.start()
        self.myThread.signal.connect(self.plotter)


class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.centralwidget = QtGui.QWidget(self)
        self.setCentralWidget(self.centralwidget)
        self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
        self.login_widget_1 = LoginWidget(self)
        self.horizontalLayout.addWidget(self.login_widget_1)
        self.login_widget_2 = LoginWidget(self)
        self.horizontalLayout.addWidget(self.login_widget_2)
        self.setCentralWidget(self.centralwidget)


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

【讨论】:

以上是关于PyQt4 中的 QThreading PyQtGraph PlotWidgets的主要内容,如果未能解决你的问题,请参考以下文章

QTableWidget 和 PyQt4 中的列宽

pyqtgraph删除pyqt4 gui中的持久图例

从pyqt4中的listWidget返回值

如何刷新嵌入在 PYQT4 中的 MatPlotlib?

pyqt4中的线程

运动生成器 2016 PyQt4