Alfa Alfa - 6 months ago 38
Python Question

python - PyQt detect when worker is finished and do something in main thread

i have an issue, I am newbie to PyQt and I can't figure out how to detect when my worker(thread got finished) using main thread(the UI thread)..
so basically what I want to says :
I have a class which create UI and a bunch of widget and when i click a button my Qthread run with success the problem is i can't detect when my thread is done...because I want to start a new UI when the thread is done .. and when i do this from inside the thread i got an error :

QPixmap: It is not safe to use pixmaps outside the GUI thread


So please if anyone knows how to detect finish SIGNAL of my thread using the Main Thread which is the UI thread

anyway here is my code .. that i tried so far

from PyQt4 import QtGui, QtCore
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from PyQt4 import QtCore


class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 400, 300)
self.setWindowTitle("Log In to your facebook account")
self.setWindowIcon(QtGui.QIcon("../data/facebook.ico"))
self.setStyleSheet("background-color: #3b5998;")

self.initUI()


def initUI(self):
"""Create facebook login ui"""
LineEditStyle = "background-color: white;padding: 10px 10px 10px 20px; font-size: 14px; font-family: consolas;" \
"border: 2px solid #3BBCE3; border-radius: 4px;"

logo = QtGui.QLabel("Facebook")
logo.setStyleSheet("font-size: 50px; font-weight:bold;color:white;")
logo.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter)

trustus = QtGui.QLabel("This Feature requires you to sign in to your facebook account.\n"
"Please enter your facebook email and password, trust us, we won't steal your \n"
"login info, you're in control + you can always go back if you want.")

email = QtGui.QLineEdit()
email.setPlaceholderText("Your facebook email address")
email.setStyleSheet(LineEditStyle)

password = QtGui.QLineEdit()
password.setEchoMode(QtGui.QLineEdit.Password)
password.setPlaceholderText("Your facebook password")
password.setStyleSheet(LineEditStyle)

login = QtGui.QPushButton("Log In")
login.clicked.connect(lambda: self.login(email.text(), password.text()))
login.setStyleSheet("background: #2A49A5;border: 1px solid #082783;color: white;padding: 10px;"
"text-decoration: none;font: 12px Verdana, sans-serif;")

keepLogin = QtGui.QCheckBox()
keepLogin.setEnabled(True)

layout = QtGui.QVBoxLayout()

self.setLayout(layout)
layout.addWidget(logo)
layout.addWidget(trustus)
layout.addStretch()
layout.addWidget(email)
layout.addWidget(password)
layout.addStretch()
layout.addWidget(login)

def look(self):
print("oh you made it, this method got run from insid the main thread insteed the ")

def login(self, email, password):
if not email.strip() or not email.find("@") or not password.strip():
print("please insert a valid entry")
else:
self.thread = LoginWorker(email, password)
self.connect(self.thread, QtCore.SIGNAL("finished()"), self.look)
self.thread.start()


class LoginWorker(QtCore.QThread):
def __init__(self, facebook_email, facebook_password, parent=None):
super(LoginWorker, self).__init__(parent)
self.email = facebook_email
self.password = facebook_password
self.driver = None

self.signal = QtCore.SIGNAL("signal")

def run(self):

if self.driver is None:
self.driver = webdriver.Firefox()
else:
self.driver.quit()
self.driver = webdriver.Firefox()
self.emit(self.signal, "hi from thread")

self.driver.get("https://wwww.facebook.com/")
self.test_login()

def test_login(self):
emailFieldID = "email"
passFieldID = "pass"
loginButtonXpath = "//input[@value='Log In']"

emailFieldElement = WebDriverWait(self.driver, 10).until(lambda driver: driver.find_element_by_id(emailFieldID))
passFieldElement = WebDriverWait(self.driver, 10).until(lambda driver: driver.find_element_by_id(passFieldID))
loginButtonElement = WebDriverWait(self.driver, 10).until(
lambda driver: driver.find_element_by_xpath(loginButtonXpath))

emailFieldElement.clear()
emailFieldElement.send_keys(self.email)

passFieldElement.clear()
passFieldElement.send_keys(self.password)

loginButtonElement.click()

self.checkLogin()

def checkLogin(self):
try:
self.driver.find_element_by_id("loginbutton")
print("You're not logged in..")
except:
"""after the execution of this line right here it will be great if i could run the method done() using the Main Thread instead of the worker.. i hope you got what I am trying to see"""
print("You're Logged in")
self.driver.quit()


def done():
print("this method (done) needs to be run from the Main Thread :(, i will be happy if you could make it runs from the Main thread.. :) because later on this print will be replaced by the code that could run my new Window..:)")


Please note that i use PyQt4

Answer

You can't define signals dynamically on instances, they have to be declared as class attributes. Also, SIGNAL is only used with the old-style syntax, which nobody uses anymore, for the new-style signal/slot syntax use pyqtSignal and pyqtSlot. Also, the arguments to the pyqtSignal should be the class types for the objects that will be emit'd through the signal. So you can't do this

self.signal = QtCore.SIGNAL("signal")

You would do this instead

class MyThread(QtCore.QThread):
    signal = QtCore.pyqtSignal(str)

    def run(self):
        self.signal.emit('The string sent through the signal')

On the other side, you should declare your slots

class Window(...):

    @QtCore.pyqtSlot(str)
    def look(self, msg):
        print msg

    def ...
        thread = MyThread()
        thread.signal.connect(self.look)

If you don't care about passing data back from the thread and only care about when it's finished

    @QtCore.pyqtSlot()
    def look(self):
        print 'Thread finished'

    def ...
        thread = MyThread()
        thread.finished.connect(self.look)