python-3.x - pyqt5中pyqtgraph的平移/缩放即使使用线程也没有响应
问题描述
我制作了一个 PyQt5 GUI,它绘制了来自 RGA 的几次扫描中的最后一次。我希望用户能够在获取下一次扫描时缩放/平移现有图,并尝试在串行通信完成时使用线程来保持 GUI 响应。那不太行。我制作了一个更简单的代码版本,带有一个 while 循环来模拟数据采集期间的暂停。同样,我发现我必须将 QApplication.processevents() 放在我的线程中,以便在线程内的每个循环结束时更新绘图 - 否则它只会在线程完成后更新最终绘图。这似乎违背了目的。
我还有一个停止按钮,我想中断所有似乎滞后的响应,类似于图表上的平移/缩放。
任何帮助,将不胜感激!
添加为 PlotWidget 的 pyqtgraph 的 UI 文件是(道歉 - 比必要的复杂,因为它是我原来的精简版本) ThreadingLayout.ui :
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1126</width>
<height>738</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="startbutton">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopbutton">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="PlotWidget" name="plotWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="exitbutton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1126</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QWidget</extends>
<header location="global">pyqtgraph</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="buttonGroup_2"/>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>
我的python3代码是:
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5.QtCore import *
import pyqtgraph as pg
from random import random
import numpy as np
import traceback, sys
class WorkerSignals(QObject):
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(int)
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add the callback to our kwargs
self.kwargs['progress_callback'] = self.signals.progress
@pyqtSlot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi("ThreadingLayout.ui", self)
self.startButton = self.findChild(QPushButton, "startbutton")
self.stopButton = self.findChild(QPushButton, "stopbutton")
self.exitButton = self.findChild(QPushButton, "exitbutton")
self.startButton.clicked.connect(self.clickedstartButton)
self.stopButton.clicked.connect(self.clickedstopButton)
self.exitButton.clicked.connect(self.clickedexitButton)
self.numberstartclicks=0
self.numberstopclicks=0
self.show()
self.threadpool = QThreadPool()
def clickedstopButton(self):
self.numberstopclicks = self.numberstopclicks+1
def plotanddelay(self):
self.plotWidget.clear()
try:
self.legend.scene().removeItem(self.legend)
except Exception as e:
pass
self.legend = self.plotWidget.addLegend()
self.singlerunx = list(range(100))
self.singleruny = []
randomscaling = random()
for i in range(100):
self.singleruny.append(randomscaling * self.singlerunx[i] + random())
self.allrunsy = np.append(self.allrunsy, self.singleruny)
# only plot last 4 plots
if self.plotrunindex < self.numberplotlines:
for self.plotlineindex in reversed(range(self.plotrunindex)):
pen = pg.mkPen(color=pg.intColor(self.plotlineindex), width=2)
ploty = self.allrunsy[len(self.allrunsy) - len(self.singlerunx) * (self.plotlineindex + 1):
len(self.allrunsy) - len(self.singlerunx) * self.plotlineindex]
self.plotWidget.plot(self.singlerunx, ploty, pen=pen,
name='Run ' + str(self.plotrunindex - self.plotlineindex))
else:
for plotlineindex in reversed(range(self.numberplotlines)):
pen = pg.mkPen(color=pg.intColor(plotlineindex), width=2)
ploty = self.allrunsy[len(self.allrunsy) - len(self.singlerunx) * (plotlineindex + 1):
len(self.allrunsy) - len(self.singlerunx) * plotlineindex]
self.plotWidget.plot(self.singlerunx, ploty, pen=pen,
name='Run ' + str(self.plotrunindex - plotlineindex))
self.plotWidget.showGrid(True, True)
QApplication.processEvents()
# put in time delay
while i < 2E7:
i = i+1
def clickedstartButton(self):
self.numberstartclicks = self.numberstartclicks+1
if (self.numberstartclicks==1):
self.plotWidget.clear()
try:
self.legend.scene().removeItem(self.legend)
except Exception as e:
pass
self.legend = self.plotWidget.addLegend()
self.allrusnx = []
self.allrunsy = []
self.numberruns = 10
self.numberplotlines = 3
for self.plotrunindex in range(self.numberruns):
if self.numberstopclicks > 0:
print('Run stopped by user')
self.numberstopclicks = 0
break
worker = Worker(self.plotanddelay())
# Execute
self.threadpool.start(worker)
else:
self.numberstartclicks = 0
def clickedexitButton(self):
self.close()
app=QApplication([])
UIWindow=UI()
app.exec()
只是一个更新 - 根据卢克的有用信息,我发现以下代码可以解决问题:
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5.QtCore import *
import pyqtgraph as pg
from random import random
import numpy as np
class ConfWorker(QThread):
threadSignal = pyqtSignal(list)
finishSignal = pyqtSignal(str)
def __init__(self, startParm):
super().__init__()
self.startParm = startParm # Initialize the parameters passed to the task
def run(self):
for i in range(self.startParm):
if (self.isInterruptionRequested()):
return
self.singlerunx = list(range(100))
returndata=[]
singleruny = []
randomscaling = random()
for j in range(100):
singleruny.append(randomscaling * self.singlerunx[j] + random())
returndata.append(i) #gives the run number
returndata.append(singleruny)
self.threadSignal.emit(returndata)
QThread.msleep(2000)
self.finishSignal.emit('finished')
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi("ThreadingLayout.ui", self)
self.startButton = self.findChild(QPushButton, "startbutton")
self.stopButton = self.findChild(QPushButton, "stopbutton")
self.exitButton = self.findChild(QPushButton, "exitbutton")
self.startButton.clicked.connect(self.workerStart)
self.stopButton.clicked.connect(self.clickedstopButton)
self.exitButton.clicked.connect(self.clickedexitButton)
self.numberstartclicks=0
self.numberstopclicks=0
self.singlerunx=[]
self.singleruny=[]
self.allrunsx=[]
self.allrunsy=[]
self.threadrunindex=0
self.startParm=0
self.show()
def clickedstopButton(self):
self.worker.requestInterruption()
print('stopped')
def updateplot(self, returndata):
self.plotWidget.clear()
self.plotrunindex=returndata[0]
self.allrunsy = np.append(self.allrunsy, returndata[1:])
self.singlerunx = list(range(100))
try:
self.legend.scene().removeItem(self.legend)
except Exception as e:
pass
self.legend = self.plotWidget.addLegend()
# only plot last 4 plots
if self.plotrunindex < self.numberplotlines:
for self.plotlineindex in reversed(range(self.plotrunindex)):
pen = pg.mkPen(color=pg.intColor(self.plotlineindex), width=2)
ploty = self.allrunsy[len(self.allrunsy) - len(self.singlerunx) * (self.plotlineindex + 1):
len(self.allrunsy) - len(self.singlerunx) * self.plotlineindex]
self.plotWidget.plot(self.singlerunx, ploty, pen=pen,
name='Run ' + str(self.plotrunindex - self.plotlineindex))
else:
for self.plotlineindex in reversed(range(self.numberplotlines)):
pen = pg.mkPen(color=pg.intColor(self.plotlineindex), width=2)
ploty = self.allrunsy[len(self.allrunsy) - len(self.singlerunx) * (self.plotlineindex + 1):
len(self.allrunsy) - len(self.singlerunx) * self.plotlineindex]
self.plotWidget.plot(self.singlerunx, ploty, pen=pen,
name='Run ' + str(self.plotrunindex - self.plotlineindex))
self.plotWidget.showGrid(True, True)
def clickedexitButton(self):
self.close()
def workerStart(self):
self.numberstartclicks = self.numberstartclicks + 1
if (self.numberstartclicks == 1):
self.plotWidget.clear()
try:
self.legend.scene().removeItem(self.legend)
except Exception as e:
pass
self.legend = self.plotWidget.addLegend()
self.allrusnx = []
self.allrunsy = []
self.numberruns = 10
self.numberplotlines = 3
self.plotrunindex = 0
self.startParm = self.numberruns
self.worker = ConfWorker(self.startParm)
self.worker.threadSignal.connect(self.updateplot)
self.worker.finishSignal.connect(self.on_finishSignal)
self.worker.start()
else:
self.numberstartclicks = 0
def on_finishSignal(self, text):
print(text)
app=QApplication([])
UIWindow=UI()
app.exec()
解决方案
这一行有几个错误:
worker = Worker(self.plotanddelay())
这一行直接调用
plotanddelay()
,意思是当点击开始按钮时,立即进入这个循环,plotanddelay
重复调用,不回到Qt事件循环。这就是 GUI 锁定的原因,以及为什么需要添加processEvents()
; 这允许您手动处理这些事件(但不是必需的)。您的 QRunnable 类期望一个函数作为其第一个参数运行,但您正在向它发送 的输出
plotanddelay()
,即None
. 所以这意味着你的后台线程根本没有做这项工作。无论如何,Qt 中的后台线程都不允许做任何 GUI 工作;因此,即使您让后台线程按
plotanddelay
预期运行,该程序也可能会崩溃。
所以要解决这个问题,你需要你的后台线程完成所有缓慢的、非 GUI 的工作(比如从你的 RGA 获取数据),然后你的主 GUI 线程完成所有的绘图工作。这可以在计时器上,以便无论数据是否到达,绘图都会定期更新,或者您可以让后台线程发送一个信号,指示新数据何时到达。
推荐阅读
- c - How does linux thread run while main () return
- docker - DNS not working between two linked docker containers - getaddrinfo EAI_AGAIN error
- c# - WPF SQLite EF "No such table" if run application from startup of shell
- php - Carbon setLocale 不工作 Laravel
- qt - Missing memberfunction of QOpenGLExtraFunctions
- python - 如何找到设置和打印值的相似之处
- three.js - THREE.js:如何使用四元数进行旋转和翻译
- html - Why does flex container have such height?
- postgresql - 如何在 PostgreSQL 中创建“lo 数据类型”
- html - 我应该使用 CSS 过滤器,因为它具有工作草案状态