如何在 PyQT 中使用 QThread

How to QThread in PyQT

我的代码有很多问题 - 特别是实现某种 signal/slot/threading。我对此很陌生,我在网上阅读的内容并没有太大帮助。

反正我用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)。

当用户单击 "Browse" 按钮时,他们可以使用 TKinter filedialogue 模块选择一个文件(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

不建议合并执行相同任务的库,更糟糕的是可以像 tkinterQt 那样被阻塞的库,它们都会创建一个内部主循环。 Qt 是一个包含许多组件的库,还包含 select 个文件: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 库都提出了不必使用这些功能的选项,并且在我们的case 组合 QEvenLoop with QTimer is the right option In addition you can not assign an item from one QListWidget to another, you must first remove it from one with 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)