1

I have been trying to get threading going with PyQt6 without much luck. I have explored multiple solutions, followed some tutorials and videos as well as looked at various questions previously asked by other users. Still, I do not seem to find a solution to make my code work.

I do understand that this could be a duplicate question as there are over 1000 questions about this topic. Therefore, I apologise if I have missed the right one.

All the solutions I have explored use __init__ or super.__init__, and what it might be done goes beyond my knowledge, I am new to OOP, and I would like to understand more about how to implement a loop inside the "GUI loop" that automatically starts when the app launches.

Could anyone kindly provide an example or guide me in the right direction on how to implement this function inside my code?

from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt, QThread, pyqtSignal
import qdarktheme
import datetime
from threading import Thread

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName('MainWindow')
        MainWindow.resize(491, 590)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName('centralwidget')
        self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(30, 100, 201, 161))
        self.groupBox.setObjectName('groupBox')
        self.timeLineEdit = QtWidgets.QLineEdit(self.groupBox)
        self.timeLineEdit.setGeometry(QtCore.QRect(10, 50, 181, 20))
        self.timeLineEdit.setObjectName('timeLineEdit')
        self.timeLabel = QtWidgets.QLabel(self.groupBox)
        self.timeLabel.setGeometry(QtCore.QRect(10, 30, 181, 10))
        self.timeLabel.setObjectName('timeLabel')
        self.saveButton = QtWidgets.QPushButton(self.centralwidget)
        self.saveButton.setGeometry(QtCore.QRect(300, 530, 121, 21))
        self.saveButton.setObjectName('saveButton')
        self.saveButton.clicked.connect(self.displayTime)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 491, 18))
        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', 'TestApp'))
        self.groupBox.setTitle(_translate('MainWindow', 'Time'))
        self.timeLabel.setText(_translate('MainWindow', 'Time'))
        self.saveButton.setText(_translate('MainWindow', 'Save'))

    def displayTime(self):
        time_now = datetime.datetime.now()
        retrieved_time = time_now.strftime('%H:%M:%S %p')
        get_time = self.timeLineEdit.setText(retrieved_time)
        print(retrieved_time)
        
    MyThread = Thread(target=displayTime)
    
    
if __name__ == '__main__':
    import sys

    app = QtWidgets.QApplication(sys.argv)
    
    def onStart():
        print('START THREAD')
        MyThread.start()
    
    QtCore.QTimer.singleShot(0, onStart)
    qdarktheme.setup_theme('light')
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec())

At the moment, the code does not provide a good solution (and is not working either) as it uses the threading library, and I would like to implement something using QThread; what I am trying to do now is to get onStart which is called as the app launches to start displayTime which is supposed to show the live time in timeLineEdit. I know that Qt has its own time, and that is a better approach, although showing the time is just an example of having an external value that is updated frequently.

UPDATE:

Following Ahmed AEK, I was able to set up a timer as follows:

if __name__ == '__main__':
    import sys

    def event_timer():
        time = datetime.datetime.now()
        global display_time
        display_time = time.strftime("%H:%M:%S %p")
        print(display_time)

    app = QtWidgets.QApplication(sys.argv)
    
    timer = QtCore.QTimer()
    timer.timeout.connect(event_timer)
    timer.start(1000)
                            
    #QtCore.QTimer.singleShot(0, onStart)
    qdarktheme.setup_theme('light')
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    
    MainWindow.show()
    sys.exit(app.exec())

Now everything works as expected, although the app crashes if I try to retrieve the time information inside MainWindow as follow:

def displayTime():
    print(display_time)
   

if __name__ == '__main__':
    import sys

    def event_timer():
        time = datetime.datetime.now()
        global display_time
        display_time = time.strftime("%H:%M:%S %p")
        ui.displayTime()
        #print(display_time)

What am I missing?

Ideally I would like displayTime to run self.timeLineEdit.setText(display_time)

floxia
  • 67
  • 1
  • 7

1 Answers1

1

GUI applications are made to work in only 1 thread, using threading on any GUI related function is an unidentified behavior and will result in a segmentation fault most of the time, or you could be more unlucky and the segmentation fault will happen some time later and you won't find the caused.

the right direction is to not use threads here at all, and just run the function that updates the GUI in the main thread.

if __name__ == '__main__':
    import sys

    app = QtWidgets.QApplication(sys.argv)


    qdarktheme.setup_theme('light')
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)

    # the following code will run in he main thread and can update the GUI.
    def onStart():
        print('START THREAD')
        ui.displayTime()

    QtCore.QTimer.singleShot(0, onStart)

    MainWindow.show()
    sys.exit(app.exec())

if the logic is more complicated you can use signals to communicate back to the main thread to do the GUI updates Updating GUI elements in MultiThreaded PyQT, but you must not update the GUI from a child thread.

a looping function can re-emit itself to allow the GUI to update itself before calling this function again.

    def displayTime(self):
        time_now = datetime.datetime.now()
        retrieved_time = time_now.strftime('%H:%M:%S %p')
        get_time = self.timeLineEdit.setText(retrieved_time)
        print(retrieved_time)
        if check_the_condition_you_would_put_in_the_loop():
            self.some_signal_that_is_connected_to_this_function.emit()

a function that just updates time is better called using a periodic Qtimer, which is the main reason the timer exists.

Ahmed AEK
  • 8,584
  • 2
  • 7
  • 23
  • Following your guidance, I was able to set up a working timer, although I cannot pass the value into the main loop. I have updated my question with the new findings. I am surely missing something; what is it that I am forgetting – floxia Dec 23 '22 at 09:58
  • 1
    @flxx you are just missing `sys.exit(app.exec())` at the end of the file. – Ahmed AEK Dec 23 '22 at 11:09
  • Thanks for spotting it, I have fixed it. Although that was just a typo, it is present In the code I am trying to run. The GUI crashes when I try to call the function in `MainWindow`, not sure what could cause that. – floxia Dec 23 '22 at 11:19
  • 1
    @flxx the above code works fine when i added the missing line, so the problem is likely not in the code you posted. – Ahmed AEK Dec 23 '22 at 11:22
  • You are correct it does work! Sorry about that, well I am happy that we managed to sort it out! – floxia Dec 23 '22 at 11:27