Unable to invoke QMessage.critical function running under different thread

Viewed 616

I am running a separate class method in a different thread (created by threading.Thread). I want to check if the user is logged in successfully or not.

If the user is not logged in, I want to prompt a message box

QtWidgets.QMessageBox.critical(
None,
"Wrong Credentials",
"The login credentials provided in settings are wrong. Kindly edit them and restart the application"
)

Error

QObject::setParent: Cannot set parent, new parent is in a different thread
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
Segmentation fault (core dumped)

Code

"""
include all related imports
"""

# Code for Ui_Dialog is not important

class Application(Ui_Dialog):
    def __init__(self, Dialog):

        self.completer = QtWidgets.QCompleter(["days", "weeks", "months"],
                                              Dialog)
        self.recent_profiles: List[Dict[str, Union[str, WebElement]]] = []
        self.parent = Dialog
        Dialog.setWindowFlags(QtCore.Qt.WindowCloseButtonHint
                              | QtCore.Qt.WindowMinimizeButtonHint)
        qtRectangle = Dialog.frameGeometry()
        centerPoint = QtWidgets.QDesktopWidget().availableGeometry().center()
        qtRectangle.moveCenter(centerPoint)
        Dialog.move(qtRectangle.topLeft())

        self.setupUi(Dialog)
        self.retranslateUi(Dialog)

        self.db = self.__load_db()

        Thread(
            target=self.__run_auto,
            args=(
                self.db["auto"],
                self.db["email"],
                self.db["password"],
            )
        ).start()

    def __run_auto(self, auto, email, password):
        if not auto["predicate"] or not auto["lim"] or not auto["template"]:
            QtWidgets.QMessageBox.critical("Error", "Auto recent sender is not configured") # the  problem occurs here
        else:
            options = Options()
            options.headless = False
            driver: WebDriver = WebDriver()
            driver.get("https://example.com/user/login")

            user: WebElement = driver.find_element_by_xpath('//*[@id="username"]')
            pasw: WebElement = driver.find_element_by_xpath('//*[@id="password"]')
            login: WebElement = driver.find_element_by_xpath(
                '/html/body/div[1]/main/div/form/div[3]/button')

            user.send_keys(email)
            pasw.send_keys(password)
            login.click()
            SUCCESS = False

            try:
                WebDriverWait(driver, 2).until(
                EC.presence_of_element_located(
                        (By.XPATH,
                            "/html/body/div[1]/main/div/form/div[2]/div/a")))
            except (NoSuchElementException, TimeoutException):
                SUCCESS = True
                pass
            if not SUCCESS:
                # the problem occurs here
                QtWidgets.QMessageBox.critical(
                    None,
                    "Wrong Credentials",
                    "The login credentials provided in settings are wrong. Kindly edit them and restart the application"
                )

            driver.quit()
        pass

    def __load_db(self):
        with open("/home/jarvis/config.json") as file:
            return json.loads(file.read())

Also, I would be thankful if you could tell me how to pass parent thread objects to the child thread.

1 Answers

You must not modify or create GUI elements from another thread, instead you must use the signals to send the information. On the other hand, do not inherit from Ui_Dialog since it is only a class that is used to fill in a widget, instead you must inherit from the appropriate widget so when you use the pyqtSlot decorator it ensures that the method will be invoked in the GUI thread.

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        # ...

    def retranslateUi(self, Dialog):
        # ...


class DriverWorker(QtCore.QObject):
    messageChanged = QtCore.pyqtSignal(str, str)

    def start(self, *args, **kwargs):
        threading.Thread(
            target=self.__run_auto, args=args, kwargs=kwargs, daemon=True
        ).start()

    def __run_auto(self, auto, email, password):
        if not auto["predicate"] or not auto["lim"] or not auto["template"]:
            self.messageChanged.emit(
                "Error", "Auto recent sender is not configured"
            )  # the  problem occurs here
        else:
            options = Options()
            options.headless = False
            driver: WebDriver = WebDriver()
            driver.get("https://example.com/user/login")

            user: WebElement = driver.find_element_by_xpath('//*[@id="username"]')
            pasw: WebElement = driver.find_element_by_xpath('//*[@id="password"]')
            login: WebElement = driver.find_element_by_xpath(
                "/html/body/div[1]/main/div/form/div[3]/button"
            )

            user.send_keys(email)
            pasw.send_keys(password)
            login.click()
            SUCCESS = False

            try:
                WebDriverWait(driver, 2).until(
                    EC.presence_of_element_located(
                        (By.XPATH, "/html/body/div[1]/main/div/form/div[2]/div/a")
                    )
                )
            except (NoSuchElementException, TimeoutException):
                SUCCESS = True
                pass
            if not SUCCESS:
                # the problem occurs here
                self.messageChanged.emit(
                    "Wrong Credentials",
                    "The login credentials provided in settings are wrong. Kindly edit them and restart the application",
                )

            driver.quit()


class Dialog(QtWidgets.QDialog, Ui_Dialog):
    def __init__(self, parent=None):
        super(Dialog, self).__init__(parent)
        self.setupUi(self)

        self.setWindowFlags(
            QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint
        )

        qtRectangle = self.frameGeometry()
        centerPoint = QtWidgets.QDesktopWidget().availableGeometry().center()
        qtRectangle.moveCenter(centerPoint)
        self.move(qtRectangle.topLeft())

        self.db = self.__load_db()

        self.worker = DriverWorker()
        self.worker.start(self.db["auto"], self.db["email"], self.db["password"])
        self.worker.messageChanged.connect(self.on_message_changed)

    @QtCore.pyqtSlot(str, str)
    def on_message_changed(self, title, description):
        QtWidgets.QMessageBox.critical(None, title, description)

    def __load_db(self):
        with open("/home/jarvis/config.json") as file:
            return json.loads(file.read())


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Dialog()
    w.show()
    sys.exit(app.exec_())
Related