Home > Blockchain >  Why is Python not updating my pyqt5 button text
Why is Python not updating my pyqt5 button text

Time:08-13

I'm having a strange issue that I can't figure out.

When I click a button, I want it to update it's own text, but in some cases it's as if it's being blocked! Excuse my rubbish coding - my first time trying to make a GUI for something. Anyways, this is my buttons function

def ConvertToSTL(self):
    if DEBUG:
        print("You Clicked on ConvertToSTL")
        
    self.btnConvertToSTL.setText("Processing...")
    self.btnConvertToSTL.setEnabled(False)      
    
    if not (FILE_OUTLINE and FILE_OTHER):
        print("You MUST select BOTH files!")
        self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)
        self.btnConvertToSTL.setEnabled(True)   
    else:
        if DEBUG:
            print("Outline: "   FILE_OUTLINE)
            if self.inputPCB.isChecked():
                print("Mask Top: "   FILE_OTHER)
            elif self.inputSolderStencil.isChecked():
                print("Paste Mask Top: "   FILE_OTHER)
                
        # Processing Files!
        outline_file = open(FILE_OUTLINE, "r")
        other_file = open(FILE_OTHER, "r")

        outline = gerber.loads(outline_file.read())
        other = gerber.loads(other_file.read())

        # outline=None
        
        if outline and other:
            output = process_gerber(
                outline,
                other,
                self.inputThickness.value(),
                self.inputIncludeLedge.isChecked,
                self.inputHeight.value(),
                self.inputGap.value(),
                self.inputIncreaseHoleSize.value(),
                self.inputReplaceRegions.isChecked(),
                self.inputFlip.isChecked(),
            )
            
            file_id = randint(1000000000, 9999999999)
            scad_filename = "./gerbertostl-{}.scad".format(file_id)
            stl_filename = "./gerbertostl-{}.stl".format(file_id)
            
            with open(scad_filename, "w") as scad_file:
                scad_file.write(output)
                
            p = subprocess.Popen(
                [
                    SCAD_BINARY,
                    "-o",
                    stl_filename,
                    scad_filename,
                ]
            )
            p.wait()

            if p.returncode:
                print("Failed to create an STL file from inputs")
            else:
                with open(stl_filename, "r") as stl_file:
                    stl_data = stl_file.read()
                os.remove(stl_filename)
                
            # Clean up temporary files
            os.remove(scad_filename)
            
            self.btnConvertToSTL.setText("Saving file...")

            saveFilename = QFileDialog.getSaveFileName(None, "Save STL", stl_filename, "STL File (*.stl)")[0]
            if DEBUG:
                print("File saved to: "   saveFilename)
            # needs a handler if user clicks cancel!
            saveFile = open(saveFilename,'w')
            saveFile.write(stl_data)
            saveFile.close()
            
            self.btnConvertToSTL.setEnabled(True)
            self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)

Now, if outline or other is FALSE for any reason - to test I manually added set outline=None then my Button DOES correctly set it's text to read "Processing...". The problem however is that if outline and other are both TRUE so the functions progresses, the button text does NOT get set to "Processing".

Instead, the button text is not changed until it reaches self.btnConvertToSTL.setText("Saving file...") which as expected sets the text correctly. Then, once the file is saved, the button again correctly updates again to the variable CONVERT_BTN_TEXT

So my question is, why on earth does the initial "Processing..." text NOT get set correctly? I don't understand

Edit: minimal reproducible example. In this case, the button text never changes to "Processing..." for me. Obviously, Requires PYQT5

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'tmp.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets
import time


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(160, 160, 421, 191))
        self.pushButton.setObjectName("pushButton")
        self.pushButton.clicked.connect(self.PushButtonClicked)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 24))
        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)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))
        
    def PushButtonClicked(self):
        print("Button Clicked")
        self.pushButton.setText("Processing")
        self.pushButton.setEnabled(False)
        
        if True and True:
            print("Sleep")
            time.sleep(5)
            print("Continue")
            self.pushButton.setText("DONE")
            self.pushButton.setEnabled(True)
            
            


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

The button will correctly say "DONE" after 5 seconds, but it never changes to "Processing"

CodePudding user response:

The issue is because the code you are invoking once the button is pressed is locking the GUI, so when it is finally released back to the main event loop it processes all of the changes at once which is why you only see the final message displayed in the button description. That is also why you can't move around the window or interact with it in any other way while the method is processing.

The solution to this is to simply not write code that freezes the GUI. This means either breaking up the process or running it in a separate thread, and using Qt's signals and slots API.

For example:

from PyQt5 import QtCore, QtWidgets
import time

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setObjectName("MainWindow")
        self.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton("Button", self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(160, 160, 421, 191))
        self.pushButton.setObjectName("pushButton")
        self.pushButton.clicked.connect(self.PushButtonClicked)
        self.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(self)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 24))
        self.menubar.setObjectName("menubar")
        self.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(self)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)

    def PushButtonClicked(self):
        self.thread = Thread(self)
        self.thread.started.connect(self.show_processing)
        self.thread.finished.connect(self.show_done)
        self.thread.start()

    def show_done(self):
        self.pushButton.setText("DONE")
        self.pushButton.setEnabled(True)

    def show_processing(self):
        self.pushButton.setText("Processing")
        self.pushButton.setEnabled(False)

class Thread(QtCore.QThread):

    def run(self):
        print("Button Clicked")
        if True and True:
            print("Sleep")
            time.sleep(5)
            print("Continue")

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

So for your specific use case, it might look something like this:

...
...
   ...
   ...
   ...

   def convert_to_STL(self):
        if DEBUG:
            print("You Clicked on ConvertToSTL")
        self.btnConvertToSTL.setText("Processing...")
        self.btnConvertToSTL.setEnabled(False)      
        if not (FILE_OUTLINE and FILE_OTHER):
            print("You MUST select BOTH files!")
            self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)
            self.btnConvertToSTL.setEnabled(True)   
        else:
            self.thread = Thread(self)
            self.thread.finished.connect(self.show_done)
            self.thread.start()

    def show_done(self):
        self.btnConvertToSTL.setEnabled(True)
        self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)

class Thread(QtCore.QThread):
    def __init__(self, parent):
        self.window = parent

    def run(self):
        if DEBUG:
            print("Outline: "   FILE_OUTLINE)
            if self.window.inputPCB.isChecked():
                print("Mask Top: "   FILE_OTHER)
            elif self.window.inputSolderStencil.isChecked():
                print("Paste Mask Top: "   FILE_OTHER)
                
        # Processing Files!
        outline_file = open(FILE_OUTLINE, "r")
        other_file = open(FILE_OTHER, "r")

        outline = gerber.loads(outline_file.read())
        other = gerber.loads(other_file.read())

        # outline=None
        
        if outline and other:
            output = process_gerber(
                outline,
                other,
                self.window.inputThickness.value(),
                self.window.inputIncludeLedge.isChecked,
                self.window.inputHeight.value(),
                self.window.inputGap.value(),
                self.window.inputIncreaseHoleSize.value(),
                self.window.inputReplaceRegions.isChecked(),
                self.window.inputFlip.isChecked(),
            )
            
            file_id = randint(1000000000, 9999999999)
            scad_filename = "./gerbertostl-{}.scad".format(file_id)
            stl_filename = "./gerbertostl-{}.stl".format(file_id)
            
            with open(scad_filename, "w") as scad_file:
                scad_file.write(output)
                
            p = subprocess.Popen(
                [
                    SCAD_BINARY,
                    "-o",
                    stl_filename,
                    scad_filename,
                ]
            )
            p.wait()

            if p.returncode:
                print("Failed to create an STL file from inputs")
            else:
                with open(stl_filename, "r") as stl_file:
                    stl_data = stl_file.read()
                os.remove(stl_filename)
                
            # Clean up temporary files
            os.remove(scad_filename)
            saveFilename = QFileDialog.getSaveFileName(None, "Save STL", stl_filename, "STL File (*.stl)")[0]
            if DEBUG:
                print("File saved to: "   saveFilename)
            # needs a handler if user clicks cancel!
            saveFile = open(saveFilename,'w')
            saveFile.write(stl_data)
            saveFile.close()


P.S. You shouldn't edit UIC files.

  • Related