如何在 PyQT 中使用 QThread
Posted
技术标签:
【中文标题】如何在 PyQT 中使用 QThread【英文标题】:How to QThread in PyQT 【发布时间】:2017-11-13 23:17:23 【问题描述】:我的代码有很多问题 - 具体来说,是实现某种信号/插槽/线程。我是新手,我在网上阅读的内容并没有太大帮助。
无论如何,我使用 PyQT Designer 制作了一个简单的 GUI。原来是这样的:
from PySide2 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(687, 514)
self.browse_btn = QtWidgets.QPushButton(Dialog)
self.browse_btn.setGeometry(QtCore.QRect(20, 440, 61, 27))
self.browse_btn.setObjectName("browse_btn")
self.play_btn = QtWidgets.QPushButton(Dialog)
self.play_btn.setGeometry(QtCore.QRect(100, 440, 61, 27))
self.play_btn.setObjectName("play_btn")
self.cur_scene_header = QtWidgets.QTextEdit(Dialog)
self.cur_scene_header.setGeometry(QtCore.QRect(20, 20, 191, 21))
self.cur_scene_header.setObjectName("cur_scene_header")
self.prev_scene_header = QtWidgets.QTextEdit(Dialog)
self.prev_scene_header.setGeometry(QtCore.QRect(20, 90, 191, 21))
self.prev_scene_header.setObjectName("prev_scene_header")
self.pause_btn = QtWidgets.QPushButton(Dialog)
self.pause_btn.setGeometry(QtCore.QRect(180, 440, 61, 27))
self.pause_btn.setObjectName("pause_btn")
self.reset_btn = QtWidgets.QPushButton(Dialog)
self.reset_btn.setGeometry(QtCore.QRect(260, 440, 61, 27))
self.reset_btn.setObjectName("reset_btn")
self.prev_scenes = QtWidgets.QListWidget(Dialog)
self.prev_scenes.setGeometry(QtCore.QRect(20, 120, 331, 301))
self.prev_scenes.setObjectName("prev_scenes")
self.current_scene = QtWidgets.QListWidget(Dialog)
self.current_scene.setGeometry(QtCore.QRect(20, 50, 331, 31))
self.current_scene.setObjectName("current_scene")
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QtWidgets.QApplication.translate("Dialog", "Dialog", None, -1))
self.browse_btn.setText(QtWidgets.QApplication.translate("Dialog", "Browse", None, -1))
self.play_btn.setText(QtWidgets.QApplication.translate("Dialog", "Play", None, -1))
self.cur_scene_header.sethtml(QtWidgets.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li white-space: pre-wrap; \n"
"</style></head><body style=\" font-family:\'Sans Serif\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Current Scene</p></body></html>", None, -1))
self.prev_scene_header.setHtml(QtWidgets.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li white-space: pre-wrap; \n"
"</style></head><body style=\" font-family:\'Sans Serif\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Previous Scenes</p></body></html>", None, -1))
self.pause_btn.setText(QtWidgets.QApplication.translate("Dialog", "Pause", None, -1))
self.reset_btn.setText(QtWidgets.QApplication.translate("Dialog", "Reset", None, -1))
那里没有什么真正重要的。有四个按钮:浏览、播放、重置和暂停。我还没有实现重置和暂停按钮,但我稍后会。
有两个QWidgetList。第一个“curr_scene”(当前场景)显示一个字符串。第二个,“prev_scenes”是先前场景的列表(过去的 current_scenes)。
当用户点击“浏览”按钮时,他们可以使用 TKinter 文件对话模块选择一个文件(一个 csv 文件)。选择后,程序将加载 csv 文件的内容。我想要接下来发生的是它转到(csv 文件的)第一行,将该文本放入 current_scene 列表中,然后等待五秒钟,将该行发送到 previous_scene 列表,然后从 csv 文件中取出下一行,将其放入 current_scene 中,依此类推,直到到达最后一行。
发生的事情现在是我想要的——除了它没有显示。它没有显示在 gui 上发生了这种情况。我只能让 prev_scene 显示文本,或者让 current_scene 显示文本,不能同时使用两者,这是通过使用repaint
完成的。
我知道我必须使用线程。但我真的不知道怎么做。能给我看看么?以下是程序其余部分的代码:
import sys
import PySide2
from PySide2.QtCore import *
from PySide2.QtWidgets import *
import pull_csv_data
import main
import time
from tkinter import filedialog
class MainDialog(QWidget, main.Ui_Dialog):
def __init__(self, parent = None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.connect(self.browse_btn, SIGNAL("clicked()"), self.browse_for_file)
self.list = []
self.connect(self.play_btn, SIGNAL("clicked()"), self.list_items)
self.csv_path = ""
self.file_watch = QFileSystemWatcher()
def browse_for_file(self):
if not self.list:
self.csv_path = filedialog.askopenfilename()
self.list = pull_csv_data.pull_data(self.csv_path)
self.update_watcher()
def update_watcher(self):
self.file_watch.addPath(self.csv_path)
self.file_watch.connect(self.file_watch, SIGNAL('fileChanged(QString)'), self.update_list)
def update_list(self):
print("here1")
old_list = self.list
self.list = pull_csv_data.pull_data(self.csv_path)
print("here2")
if len(old_list) < len(self.list):
for i in range(len(old_list), len(self.list)):
item = QListWidgetItem(self.list[i][0])
self.prev_scenes.addItem(item)
print("here6")
else:
self.list = pull_csv_data.pull_data(self.csv_path)
self.prev_scenes.clear()
for i in self.list:
item = QListWidgetItem(i[0])
self.prev_scenes.addItem(item)
def list_items(self):
if self.prev_scenes.count() == 0:
for i in self.list:
item = QListWidgetItem(i[0])
self.update_current(item)
def update_current(self, item):
self.current_scene.addItem(item)
time.sleep(0.5)
self.update_prev(item)
self.current_scene.clear()
def update_prev(self, item):
print("hello")
self.prev_scenes.addItem(item)
self.prev_scenes.repaint()
self.prev_scenes.repaint()
app = QApplication(sys.argv)
form = MainDialog()
form.show()
sys.exit(app.exec_())
这里是 pull_csv_data:
def pull_data(file_path):
king = []
with open(file_path) as csvDataFile:
csvReader = csv.reader(csvDataFile)
for row in csvReader:
king.append(row)
return king
【问题讨论】:
什么是 pull_csv_data? 这是一个函数...让我发布它。它只从 csv 文件中提取数据并将其保存在列表中。 如果是函数,请放。 你可以举一个.csv文件的例子 @eyllanesc 不要担心 csv 文件......它不相关。想象一下,您只有一个包含 50 个字符串的列表分配给 self.list。 【参考方案1】:不建议将执行相同任务的库组合在一起,更糟糕的库可以被阻止为 tkinter
和 Qt
并且都创建内部主循环。 Qt 是一个库,它包含许多组件,还可以选择文件:QFileDialog.getOpenFileName()
def browse_for_file(self):
if not self.list:
self.csv_path, _ = QFileDialog.getOpenFileName()
self.list = pull_csv_data.pull_data(self.csv_path)
self.update_watcher()
另一件不应该做的事情是做诸如sleep()
之类的阻塞任务,这些任务不会让GUI工作,每个GUI库都提出了不必使用这些功能的选项,在我们的例子中是QEvenLoop
和 QTimer
是正确的选项此外,您不能将一个项目从一个 QListWidget
分配给另一个,您必须先将其从一个 takeItem()
删除并分配给另一个:
def update_current(self, item):
self.current_scene.addItem(item)
loop = QEventLoop()
QTimer.singleShot(500, loop.quit)
loop.exec_()
it = self.current_scene.takeItem(0)
self.update_prev(it)
def update_prev(self, item):
self.prev_scenes.addItem(item)
【讨论】:
非常感谢,成功了!你有一些好的资源来学习线程吗?我应该什么时候使用它?我担心我认为我应该在这里使用它,而我不必这样做 基本 Qt 规则:GUI 不应直接从与主线程不同的线程更新,该线程称为 GUI 线程:doc.qt.io/qt-5/thread-basics.html#gui-thread-and-worker-thread,在您的情况下,您想这样做看看对于另一种选择,上面我并不是说您不能使用来自另一个线程的数据来更新 GUI,但是您必须使用信号,或者可能是 QMetaObject::invokeMethod,我也建议使用与 Qt 相关的线程或类,而不是使用其他类型的线程,例如 QThread、QRunnable、QThreadPool。 Qt 文档非常好,推荐阅读。以上是关于如何在 PyQT 中使用 QThread的主要内容,如果未能解决你的问题,请参考以下文章
如何在Python PyQt中中断QThread上的脚本执行?
PyQt5:如何在 QThread 和某些子类之间“通信”?