我目前正在关注PyQt中的线程这个教程(来自这里的代码).由于它是用PyQt4(和Python2)编写的,我调整了代码以使用PyQt5和Python3.
这是gui文件(newdesign.py
):
# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'threading_design.ui' # # Created by: PyQt5 UI code generator 5.6 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(526, 373) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName("verticalLayout") self.subreddits_input_layout = QtWidgets.QHBoxLayout() self.subreddits_input_layout.setObjectName("subreddits_input_layout") self.label_subreddits = QtWidgets.QLabel(self.centralwidget) self.label_subreddits.setObjectName("label_subreddits") self.subreddits_input_layout.addWidget(self.label_subreddits) self.edit_subreddits = QtWidgets.QLineEdit(self.centralwidget) self.edit_subreddits.setObjectName("edit_subreddits") self.subreddits_input_layout.addWidget(self.edit_subreddits) self.verticalLayout.addLayout(self.subreddits_input_layout) self.label_submissions_list = QtWidgets.QLabel(self.centralwidget) self.label_submissions_list.setObjectName("label_submissions_list") self.verticalLayout.addWidget(self.label_submissions_list) self.list_submissions = QtWidgets.QListWidget(self.centralwidget) self.list_submissions.setBatchSize(1) self.list_submissions.setObjectName("list_submissions") self.verticalLayout.addWidget(self.list_submissions) self.progress_bar = QtWidgets.QProgressBar(self.centralwidget) self.progress_bar.setProperty("value", 0) self.progress_bar.setObjectName("progress_bar") self.verticalLayout.addWidget(self.progress_bar) self.buttons_layout = QtWidgets.QHBoxLayout() self.buttons_layout.setObjectName("buttons_layout") self.btn_stop = QtWidgets.QPushButton(self.centralwidget) self.btn_stop.setEnabled(False) self.btn_stop.setObjectName("btn_stop") self.buttons_layout.addWidget(self.btn_stop) self.btn_start = QtWidgets.QPushButton(self.centralwidget) self.btn_start.setObjectName("btn_start") self.buttons_layout.addWidget(self.btn_start) self.verticalLayout.addLayout(self.buttons_layout) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Threading Tutorial - nikolak.com ")) self.label_subreddits.setText(_translate("MainWindow", "Subreddits:")) self.edit_subreddits.setPlaceholderText(_translate("MainWindow", "python,programming,linux,etc (comma separated)")) self.label_submissions_list.setText(_translate("MainWindow", "Submissions:")) self.btn_stop.setText(_translate("MainWindow", "Stop")) self.btn_start.setText(_translate("MainWindow", "Start"))
和主脚本(main.py
):
from PyQt5 import QtWidgets from PyQt5.QtCore import QThread, pyqtSignal, QObject import sys import newdesign import urllib.request import json import time class getPostsThread(QThread): def __init__(self, subreddits): """ Make a new thread instance with the specified subreddits as the first argument. The subreddits argument will be stored in an instance variable called subreddits which then can be accessed by all other class instance functions :param subreddits: A list of subreddit names :type subreddits: list """ QThread.__init__(self) self.subreddits = subreddits def __del__(self): self.wait() def _get_top_post(self, subreddit): """ Return a pre-formatted string with top post title, author, and subreddit name from the subreddit passed as the only required argument. :param subreddit: A valid subreddit name :type subreddit: str :return: A string with top post title, author, and subreddit name from that subreddit. :rtype: str """ url = "https://www.reddit.com/r/{}.json?limit=1".format(subreddit) headers = {'User-Agent': 'nikolak@outlook.com tutorial code'} request = urllib.request.Request(url, header=headers) response = urllib.request.urlopen(request) data = json.load(response) top_post = data['data']['children'][0]['data'] return "'{title}' by {author} in {subreddit}".format(**top_post) def run(self): """ Go over every item in the self.subreddits list (which was supplied during __init__) and for every item assume it's a string with valid subreddit name and fetch the top post using the _get_top_post method from reddit. Store the result in a local variable named top_post and then emit a pyqtSignal add_post(QString) where QString is equal to the top_post variable that was set by the _get_top_post function. """ for subreddit in self.subreddits: top_post = self._get_top_post(subreddit) self.emit(pyqtSignal('add_post(QString)'), top_post) self.sleep(2) class ThreadingTutorial(QtWidgets.QMainWindow, newdesign.Ui_MainWindow): """ How the basic structure of PyQt GUI code looks and behaves like is explained in this tutorial http://nikolak.com/pyqt-qt-designer-getting-started/ """ def __init__(self): super(self.__class__, self).__init__() self.setupUi(self) self.btn_start.clicked.connect(self.start_getting_top_posts) def start_getting_top_posts(self): # Get the subreddits user entered into an QLineEdit field # this will be equal to '' if there is no text entered subreddit_list = str(self.edit_subreddits.text()).split(',') if subreddit_list == ['']: # since ''.split(',') == [''] we use that to check # whether there is anything there to fetch from # and if not show a message and abort QtWidgets.QMessageBox.critical(self, "No subreddits", "You didn't enter any subreddits.", QtWidgets.QMessageBox.Ok) return # Set the maximum value of progress bar, can be any int and it will # be automatically converted to x/100% values # e.g. max_value = 3, current_value = 1, the progress bar will show 33% self.progress_bar.setMaximum(len(subreddit_list)) # Setting the value on every run to 0 self.progress_bar.setValue(0) # We have a list of subreddits which we use to create a new getPostsThread # instance and we pass that list to the thread self.get_thread = getPostsThread(subreddit_list) # Next we need to connect the events from that thread to functions we want # to be run when those pyqtSignals get fired # Adding post will be handeled in the add_post method and the pyqtSignal that # the thread will emit is pyqtSignal("add_post(QString)") # the rest is same as we can use to connect any pyqtSignal self.connect(self.get_thread, pyqtSignal("add_post(QString)"), self.add_post) # This is pretty self explanatory # regardless of whether the thread finishes or the user terminates it # we want to show the notification to the user that adding is done # and regardless of whether it was terminated or finished by itself # the finished pyqtSignal will go off. So we don't need to catch the # terminated one specifically, but we could if we wanted. self.connect(self.get_thread, pyqtSignal("finished()"), self.done) # We have all the events we need connected we can start the thread self.get_thread.start() # At this point we want to allow user to stop/terminate the thread # so we enable that button self.btn_stop.setEnabled(True) # And we connect the click of that button to the built in # terminate method that all QThread instances have self.btn_stop.clicked.connect(self.get_thread.terminate) # We don't want to enable user to start another thread while this one is # running so we disable the start button. self.btn_start.setEnabled(False) def add_post(self, post_text): """ Add the text that's given to this function to the list_submissions QListWidget we have in our GUI and increase the current value of progress bar by 1 :param post_text: text of the item to add to the list :type post_text: str """ self.list_submissions.addItem(post_text) self.progress_bar.setValue(self.progress_bar.value()+1) def done(self): """ Show the message that fetching posts is done. Disable Stop button, enable the Start one and reset progress bar to 0 """ self.btn_stop.setEnabled(False) self.btn_start.setEnabled(True) self.progress_bar.setValue(0) QtWidgets.QMessageBox.information(self, "Done!", "Done fetching posts!") def main(): app = QtWidgets.QApplication(sys.argv) form = ThreadingTutorial() form.show() app.exec_() if __name__ == '__main__': main()
现在我收到以下错误:
AttributeError: 'ThreadingTutorial' object has no attribute 'connect'
谁能告诉我如何解决这个问题?任何帮助将一如既往地非常感谢.
使用QObject.connect()
和PyQt4中类似的被称为"老式的信号",并在PyQt5不支持了,它仅支持"新风格的信号",它已经在PyQt4的是连接信号的推荐方式.
在PyQt5中,您需要直接使用绑定信号的connect()
和emit()
方法,例如,而不是:
self.emit(pyqtSignal('add_post(QString)'), top_post) ... self.connect(self.get_thread, pyqtSignal("add_post(QString)"), self.add_post) self.connect(self.get_thread, pyqtSignal("finished()"), self.done)
使用:
self.add_post.emit(top_post) ... self.get_thread.add_post.connect(self.add_post) self.get_thread.finished.connect(self.done)
但是要使其工作,您需要add_post
在getPostsThread
第一个上明确定义信号,否则您将收到属性错误.
class getPostsThread(QThread): add_post = pyqtSignal(str) ...
在使用旧信号的PyQt4中,当信号被使用时它被自动定义,现在需要明确地完成.