pythonpyqtpyqt5python-multithreadingqmessagebox

Info-Error Dialogue Management in Threading application in PyQt5


I'm new to GUI-programming and need help with a QThread application. I have a gui program that makes a query and lists the results of the query on the screen. I want an information dialog to open when the listing on the screen is finished. I wrote a function for this and when the operation is successful, it works without any problems. But I couldn't figure out how to show an error dialog if an invalid link entry occurs. Thank you for helping.

-I have commented out some of the methods I tried and failed.

import sys
import requests
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox
from bs4 import BeautifulSoup
from time import sleep


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(476, 391)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(0, 270, 471, 81))
        self.pushButton.setObjectName("pushButton")
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(0, 0, 471, 31))
        self.lineEdit.setObjectName("lineEdit")
        self.listWidget = QtWidgets.QListWidget(self.centralwidget)
        self.listWidget.setGeometry(QtCore.QRect(0, 50, 471, 192))
        self.listWidget.setObjectName("listWidget")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 476, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        self.running = False
        self.pushButton.clicked.connect(self.startBs4Worker)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "GET"))

    def reveiceData(self, data):
        # This method is the method that will 
        # process the data sent by the thread.
        if data == None:
            return

        _translate = QtCore.QCoreApplication.translate
        item = QtWidgets.QListWidgetItem()
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(11)
        item.setFont(font)
        item.setCheckState(QtCore.Qt.Checked)
        item.setText(_translate("MainWindow", data))
        self.listWidget.addItem(item)

    def setThreadStatus(self):
        self.running = not self.running

    def startBs4Worker(self):
        if self.running:
            print("Thread is still running ...")
            return
        else:
            self.thread = QThread()

            # We send the URL address that the thread will process as a parameter.
            self.worker = Bs4Worker(url=self.lineEdit.text())

            self.worker.moveToThread(self.thread)
            self.thread.started.connect(self.worker.run)
            self.worker.finished.connect(self.thread.quit)
            self.worker.finished.connect(self.worker.deleteLater)
            self.worker.finished.connect(self.setThreadStatus)
            self.thread.finished.connect(self.thread.deleteLater)
            self.worker.notifyProgress.connect(self.reveiceData)
            self.worker.finished.connect(self.uyariBox) #---> With this place active, I couldn't figure out how to activate the Error message when wrong connection was entered.
            self.running = True
            self.thread.start()


    def uyariBox(self):
        self.msg = QMessageBox()
        self.msg.thread()
        self.msg.setWindowTitle("Bilgi !")
        self.msg.setText("Tüm Sonuçlar Getirildi")
        self.msg.setIcon(QMessageBox.Information)
        self.msg.exec_()



class Bs4Worker(QThread):
    notifyProgress = QtCore.pyqtSignal(str)

    def __init__(self, url, parent=None):
        QThread.__init__(self, parent)
        self.url = url

    def run(self):
        try:
            r = requests.get(self.url)
            soup = BeautifulSoup(r.content)
            linkler = soup.find_all("a")
            for link in linkler:
                baslik = link.get("title")

                # Sending header information to master class
                self.notifyProgress.emit(baslik)
            self.finished.connect(ui.uyariBox) #---> When you activate this place, the warning appears instantly and disappears immediately.
                                               #--->  and there is no click anywhere in the app. (Since completion is not pressed in the warning message)

            self.finished.emit()

           # self.finished.connect(ui.uyariBox) #----> Warning does not appear when you activate this place.


        except:

            print("link error")
            self.finished.emit()
            # When I create a new QMessageBox to show the error message, the application crashes.
    
Uygulama = QApplication(sys.argv)
menu = QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(menu)
menu.show()
sys.exit(Uygulama.exec_())

Solution

  • You can pass the operation result as string to the uyariBox method.

    First you can create another signal in Bs4Worker:

    class Bs4Worker(QThread):
        result = QtCore.pyqtSignal(str)
    

    Change the previous worker.finished connection to worker.result:

    def startBs4Worker(self):
        else:
             self.worker.result.connect(self.uyariBox)
             #Previous was self.worker.finished.connect(self.uyariBox)
    

    Add variable to function:

    def uyariBox(self,message):
        self.msg = QMessageBox()
        self.msg.thread()
        self.msg.setWindowTitle("Bilgi !")
        self.msg.setText(message)
        self.msg.setIcon(QMessageBox.Information)
        self.msg.exec_()
    

    Then use signal to pass on the result of the operation:

        def run(self):
            try:
                self.result.emit("Tüm Sonuçlar Getirildi")
            except Exception as e:
                self.result.emit(f"Error: {e}")
    

    Full code:

    import sys
    import requests
    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt5.QtCore import QThread, pyqtSignal
    from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox
    from bs4 import BeautifulSoup
    from time import sleep
    
    
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(476, 391)
            self.centralwidget = QtWidgets.QWidget(MainWindow)
            self.centralwidget.setObjectName("centralwidget")
            self.pushButton = QtWidgets.QPushButton(self.centralwidget)
            self.pushButton.setGeometry(QtCore.QRect(0, 270, 471, 81))
            self.pushButton.setObjectName("pushButton")
            self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
            self.lineEdit.setGeometry(QtCore.QRect(0, 0, 471, 31))
            self.lineEdit.setObjectName("lineEdit")
            self.listWidget = QtWidgets.QListWidget(self.centralwidget)
            self.listWidget.setGeometry(QtCore.QRect(0, 50, 471, 192))
            self.listWidget.setObjectName("listWidget")
            MainWindow.setCentralWidget(self.centralwidget)
            self.menubar = QtWidgets.QMenuBar(MainWindow)
            self.menubar.setGeometry(QtCore.QRect(0, 0, 476, 21))
            self.menubar.setObjectName("menubar")
            MainWindow.setMenuBar(self.menubar)
            self.statusbar = QtWidgets.QStatusBar(MainWindow)
            self.statusbar.setObjectName("statusbar")
            MainWindow.setStatusBar(self.statusbar)
    
            self.retranslateUi(MainWindow)
            QtCore.QMetaObject.connectSlotsByName(MainWindow)
    
            self.running = False
            self.pushButton.clicked.connect(self.startBs4Worker)
    
        def retranslateUi(self, MainWindow):
            _translate = QtCore.QCoreApplication.translate
            MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
            self.pushButton.setText(_translate("MainWindow", "GET"))
    
        def reveiceData(self, data):
            # This method is the method that will
            # process the data sent by the thread.
            if data == None:
                return
    
            _translate = QtCore.QCoreApplication.translate
            item = QtWidgets.QListWidgetItem()
            font = QtGui.QFont()
            font.setFamily("Arial")
            font.setPointSize(11)
            item.setFont(font)
            item.setCheckState(QtCore.Qt.Checked)
            item.setText(_translate("MainWindow", data))
            self.listWidget.addItem(item)
    
        def setThreadStatus(self):
            self.running = not self.running
    
        def startBs4Worker(self):
            if self.running:
                print("Thread is still running ...")
                return
            else:
                self.thread = QThread()
    
                # We send the URL address that the thread will process as a parameter.
                self.worker = Bs4Worker(url=self.lineEdit.text())
    
                self.worker.moveToThread(self.thread)
                self.thread.started.connect(self.worker.run)
                self.worker.finished.connect(self.thread.quit)
                self.worker.finished.connect(self.worker.deleteLater)
                self.worker.finished.connect(self.setThreadStatus)
                self.thread.finished.connect(self.thread.deleteLater)
                self.worker.notifyProgress.connect(self.reveiceData)
                self.worker.result.connect(
                    self.uyariBox)  # ---> With this place active, I couldn't figure out how to activate the Error message when wrong connection was entered.
                self.running = True
                self.thread.start()
    
        def uyariBox(self,message):
            self.msg = QMessageBox()
            self.msg.thread()
            self.msg.setWindowTitle("Bilgi !")
            self.msg.setText(message)
            self.msg.setIcon(QMessageBox.Information)
            self.msg.exec_()
    
    
    class Bs4Worker(QThread):
        result = QtCore.pyqtSignal(str)
        notifyProgress = QtCore.pyqtSignal(str)
    
        def __init__(self, url, parent=None):
            QThread.__init__(self, parent)
            self.url = url
    
        def run(self):
            try:
                r = requests.get(self.url)
                soup = BeautifulSoup(r.content)
                linkler = soup.find_all("a")
                for link in linkler:
                    baslik = link.get("title")
    
                    # Sending header information to master class
                    self.notifyProgress.emit(baslik)
                self.finished.connect(
                    ui.uyariBox)  # ---> When you activate this place, the warning appears instantly and disappears immediately.
                # --->  and there is no click anywhere in the app. (Since completion is not pressed in the warning message)
    
                self.finished.emit()
                self.result.emit("Tüm Sonuçlar Getirildi")
            # self.finished.connect(ui.uyariBox) #----> Warning does not appear when you activate this place.
    
            except Exception as e:
    
                print("link error")
                self.finished.emit()
                self.result.emit(f"Error: {e}")
                # When I create a new QMessageBox to show the error message, the application crashes.
    
    
    Uygulama = QApplication(sys.argv)
    menu = QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(menu)
    menu.show()
    sys.exit(Uygulama.exec_())