How to call a function periodically while my MainWindow is active? - python

I have tried doing it with multiprocessing module to no avail. I get the following error:
TypeError: cannot pickle 'MainWindow' object
import time, multiprocessing
from PyQt5 import QtWidgets, QtGui
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
self.show()
def initializeUI(self):
# UI stuff
self.setLayout(QtWidgets.QGridLayout())
dummy_btn = QtWidgets.QPushButton("Ok")
self.layout().addWidget(dummy_btn)
updater = multiprocessing.Process(target=self.update_function, args=[])
updater.start()
def update_function(self):
time.sleep(2)
print("This text, again!")
self.update_function()
app = QtWidgets.QApplication([])
mw = MainWindow()
app.exec_()

the proper way to do this is to not use any sort of parallel mechanism, instead use QTimer.singleshot, as QT doesn't work well with multiprocessing or threading, and if you want to repeat it then you can just connect the function to a Qtimer.timeout signal and set the timer on repeat using Qtimer.start() as in this tutorial
import time, multiprocessing
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QTimer
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
self.show()
def initializeUI(self):
# UI stuff
self.setLayout(QtWidgets.QGridLayout())
dummy_btn = QtWidgets.QPushButton("Ok")
self.layout().addWidget(dummy_btn)
self.timer = QTimer(self)
# self.timer.singleShot(2000,self.update_function) # for one time call only
self.timer.timeout.connect(self.update_function)
self.timer.start(2000) # time in milliseconds.
def update_function(self):
# time.sleep(2) this is not needed
print("This text, again!")
self.update() # this had a typo
app = QtWidgets.QApplication([])
mw = MainWindow()
app.exec_()
Edit: to clarify on working with threads and multiprocessing, if you use multiprocessing for example there are many precautions, such as putting an if __name__ == "__main__": guard on your code, and not use anything that belong to QT inside the subprocesses, and just use it for running things that don't need QT, like reading files and doing calculations.
as for threading, using any QWidget object in another thread other than your main application thread is going to crash your application, you can emit signals from child threads for signaling, but you cannot update the GUI on another thread, so only use QT objects that don't touch the GUI inside threads. (like networking, reading files, and sharing the CPU for extra calculations)

Related

In PyQt5 How to call function or method after load aplication?

from PyQt5 import QtWidgets
import time
def show_message(self):
time.sleep(5)
self.label.setText("It's me")
class Main(QtWidgets.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.label = QtWidgets.QLabel('Hello', self)
#How to call this func after load application
show_message(self)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication([])
application = Main()
application.show()
sys.exit(app.exec_())
How to call show_message(self) after load application
Does pyqt5 have a function or method like self.afterLoad(application, show_message)
It seems to me something like this available on tkinter
I am guessing you want to start the application and then after 5 seconds the text in the label will get changed to the new text.
What you have right now is almost working, however the problem is that when you call time.sleep(5) then the whole execution of the program will get paused and nothing gets shown for that amount of time. If you still wanna be able to interact with the program during those 5 seconds then you will need to use a timer that is running in the background instead.
PyQt already has something like that in PyQt5.QtCore.QTimer. If you use that then your code could look something like this
from PyQt5 import QtWidgets
from PyQt5.QtCore import QTimer # Importing QTimer
import time
def show_message(self):
self.label.setText("It's me")
# Stopping the timer. Otherwise it will run over and over again.
self.timer.stop()
class Main(QtWidgets.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.label = QtWidgets.QLabel('Hello', self)
# Create a new QTimer
self.timer = QTimer(self)
# Tell the timer that it should call show_message(self) when the time runs out
self.timer.timeout.connect(lambda: show_message(self))
# Start the timer which then starts running in the background for 5 seconds
self.timer.start(5000)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication([])
application = Main()
application.show()
sys.exit(app.exec_())
One last thing that I would recommend doing is putting your functions that belong to a class inside of it. So here it would probably be better to put show_message inside of the main class because right now you can call the function from every part of the code and that can lead to errors.

How to structure large pyqt5 GUI without subclassing QThread and using QPushButtons to do long-running tasks

I'm looking at creating a program with a PyQt5 GUI. The program will start with a UI with numerous buttons. These buttons will be used to open other programs/completed long running tasks. I know I need to use QThread, but I am unsure how to structure the programs so that it scales properly.
I've been at this for ages and have read numerous posts/tutorials. Most lean down the subclassing route. In the past, I have managed to create a working program subclassing QThread, but I have since read that this metholodogy is not preferred.
I have a feeling I should be creating a generic worker and passing in a function with *args and **kwargs, but that is not in my skillset yet.
I originally created a thread for each button during the GUI init, but that seemed like it was going to get out of hand quickly.
I am currently at the stage of creating a thread under the slot connected to the button.clicked signal. I am not sure if I then have to have a worker for each button or if I can/should make a generic worker and pass in a function. Note: I have tried to do this but have not been able to do it.
#Import standard modules
import sys
#Import third-party modles
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton, QVBoxLayout, QWidget
class Worker(QObject):
#Custom signals?? or built-in QThread signals?
started = pyqtSignal()
finished = pyqtSignal()
def __init__(self):
super().__init__()
self.started.emit()
#pyqtSlot()
def do_something(self):
for _ in range(3):
print('Threading...')
QThread.sleep(1)
self.finished.emit()
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
#Create GUI
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget )
self.vertical_layout = QVBoxLayout(self.centralWidget)
self.setWindowTitle('QThread Test')
self.setGeometry(300, 300, 300, 50)
self.button1=QPushButton("Task 1", self, clicked=self._task1_clicked)
self.button2=QPushButton("Task 2", self, clicked=self._task2_clicked)
self.vertical_layout.addWidget(self.button1)
self.vertical_layout.addWidget(self.button2)
self.vertical_layout.addStretch()
def _task1_clicked(self):
print('task1 clicked')
#Create the worker
self.my_worker = Worker()
#Create thread; needs to be done before connecting signals/slots
self.task1_thread = QThread()
#Move the worker to the thread
self.my_worker.moveToThread(self.task1_thread)
#Connect worker and thread signals to slots
self.task1_thread.started.connect(self._thread_started)
self.task1_thread.started.connect(self.my_worker.do_something)
self.my_worker.finished.connect(self._thread_finished)
#Start thread
self.task1_thread.start()
def _task2_clicked(self):
print('task2 clicked')
def _thread_started(self):
print('thread started')
def _thread_finished(self):
print('thread finished')
self.my_worker.isRunning = False
self.task1_thread.quit()
self.task1_thread.wait()
print('The thread is running: ' + str(self.task1_thread.isRunning()))
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Window()
form.show()
app.exec_()
The above seems to work, but I feel like I have stumbled on to it and it is not the correct way of doing this. I do not want this to be my 'go-to' method if it is completely wrong. I'd like to be able to generate more complicated (more buttons doing things) programs compared to a one button/one task program.
In addition, I can't seem to get the QThread started and finished signals to fire without basically making them custom built signals. This is one reason I think I am going about this wrong.
from PyQt5 import QtCore
class AsyncTask(QtCore.QThread):
taskDone = QtCore.pyqtSignal(dict)
def __init__(self, *, task, callback=None, parent = None):
super().__init__(parent)
self.task = task
if callback != None:
self.taskDone.connect(callback)
if callback == None:
callback = self.callback
self.start()
def run(self):
try:
result = self.task()
print(result)
self.taskDone.emit(result)
except Exception as ex:
print(ex)
def callback(self):
print('callback')
Please try code above, call like this:
AsyncTask(task=yourTaskFunction, callback=yourCallbackFunction)

Is my threading proper ? if yes then why code is not working?

I am creating an alarm clock in python using PyQt4 and in that I am using LCD display widget, which display current updating time. For that I am using threading. But I am new to it so the problem is I have no clue how to debug that thing.
This is my code
import sys
from PyQt4 import QtGui, uic
import time
import os
from threading import Thread
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
uic.loadUi('AlarmClock_UI.ui', self)
self.show()
self.comboBox.setCurrentIndex(0)
self.comboBox.currentIndexChanged.connect(self.getSelection)
self.lineEdit.setText('Please select the reminder type')
timeThread = Thread(target = self.showTime())
timeThread.start()
def getSelection(self):
if self.comboBox.currentIndex() == 1:
self.lineEdit.setText('Select the alarm time of your choice')
elif self.comboBox.currentIndex() == 2:
self.lineEdit.setText('Use those dials to adjust hour and minutes')
else:
self.lineEdit.setText('Please select the reminder type')
def showTime(self):
showTime = time.strftime('%H:%M:%S')
self.lcdNumber.display(showTime)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())
I tried while loop in showTime() function then it was not even loading GUI just running in the background.
Thanks :)
As has been said elsewhere, you do not need to use threading for this, as a simple timer will do. Here is a basic demo script:
import sys
from PyQt4 import QtCore, QtGui
class Clock(QtGui.QLCDNumber):
def __init__(self):
super(Clock, self).__init__(8)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
self.showTime()
def showTime(self):
time = QtCore.QTime.currentTime()
self.display(time.toString('hh:mm:ss'))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Clock()
window.setWindowTitle('Clock')
window.setGeometry(500, 100, 400, 100)
window.show()
sys.exit(app.exec_())
Qt does not support doing GUI operations in threads other than the main thread. So when you call self.lcddisplay.display(showTime) from within the context of your spawned thread, that is an error and Qt will not work correctly.
As tdelaney suggested in his comment, the best way to handle this sort of thing is to use a QTimer to emit a signal at the appropriate intervals, and update your lcddisplay in the slot that signal is connected to.
(if you insist on using threads, however, e.g. as a learning exercise, then your spawned thread would need to send a message to the main thread to tell the main thread to do the display update, rather than trying to do the update itself)

How to create PyQt5 GUI that does not occupy the main thread

Is it possible to get Python to launch a PyQt5 GUI application using the main thread of execution, and then leave the thread open to do other things?
Here is the simplest example I can think of:
from PyQt5.QtWidgets import *
def infinite_useless_loop():
while True:
pass
app = QApplication([])
doodad = QMainWindow()
doodad.show()
infinite_useless_loop()
If you run this code, it freezes because the 'mainloop' is not activated as it normally would be if I didn't make a call to the useless infinite loop, and instead put a call to app.exec_(). The call to infinite_useless_loop is meant to substitute the main thread being used to do other useful work WHILE the GUI is running correctly.
To do this correctly, I'd have to imagine one would make a separate thread and use that to run the GUI, while launching that thread with the main thread, and just continuing on from there; able to communicate if necessary in the future.
EDIT
I wrote some code that gets the main event loop working in a seperate thread, but I am still not able to fully interact with the GUI object that is created in another thread. This code effectively solves half of my problem:
from PyQt5.QtWidgets import *
from threading import Thread
import sys
def makeGUI():
app = QApplication([])
doodad = QMainWindow()
doodad.show()
sys.exit(app.exec_())
def infinite_useless_loop():
while True:
pass
if __name__ == '__main__':
thread = Thread(target=makeGUI)
thread.start()
infinite_useless_loop()
This code spawns the blank window, able to be clicked on and resized correctly (because the event loop is working), but is on its own. Do I have to revert to signals and slots to communicate with the object, or can I somehow get the GUI object back into the main thread and use it's methods from there?
EDIT 2
I do not wish to do any actual GUI manipulation outside of the GUI's thread, just call methods on the GUI object that I HAVE MADE. The entire GUI structure would be made by the GUI object.
EDIT 3
This new code now makes it able to be communicated with, but only with raw bits of information, and not by one thread accessing another thread's GUI object's methods. This is close, but its still not what I want. Calling the GUI object's methods would have to be done by passing the raw information through the queue, and then being processed at the other side. Then an exec() function would have to be called. That is not acceptable. Here is the code:
from PyQt5.QtWidgets import *
import multiprocessing
import threading
import sys
import time
import queue
class Application(QMainWindow):
def __init__(self, comms):
super(Application, self).__init__()
self.comms = comms
self.fetchingThread = threading.Thread(target=self.fetchingLoop)
self.labelOne = QLabel(self)
self.layout = QVBoxLayout()
self.layout.addWidget(self.labelOne)
self.setLayout(self.layout)
self.fetchingThread.start()
def fetchingLoop(self):
while True:
try:
obj = self.comms.get()
self.processItem(obj)
except queue.Empty:
pass
def processItem(self, item):
self.labelOne.setText(str(item))
print(self.labelOne.text())
class Launcher(multiprocessing.Process):
def __init__(self, Application):
super(Launcher, self).__init__()
self.comms = multiprocessing.Queue()
self.Application = Application
def get_comms(self):
return self.comms
def run(self):
app = QApplication([])
window = self.Application(self.comms)
window.show()
sys.exit(app.exec_())
def no_thread():
app = QApplication([])
window = Application(False)
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
thread = Launcher(Application)
comms = thread.get_comms()
thread.start()
c = 0
while True:
c += 1
time.sleep(1)
comms.put(c)

Communicate from Main Application Thread to Seperate QThread

I have this basic app working. It creates a new thread, and starts it. Then it uses signals to communicate back to the main thread for something else to happen.
My question is how do I pass data from the main thread to the new thread that is created, this part really doesn't make sense to me. Or is there another way altogether to do threading back and forth. Essentially the main thread and the new thread will run for the entire life of the application, so they need to communicate back and forth.
As a note I am a web developer so native apps are new to me. Also I am still new to qt and pyqt so not sure how to do this.
import sys
from PyQt4 import QtGui
from PyQt4.QtCore import QThread, pyqtSignal
class Thread(QThread):
message_recieved = pyqtSignal(object)
def run(self):
self.message_recieved.emit('hello')
class Main(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.initUI()
self.thread = Thread()
self.thread.message_recieved.connect(self.message)
self.thread.start()
def message(self, msg):
print msg
def initUI(self):
self.setGeometry(100, 100, 800, 600)
self.setWindowTitle("Test App")
def main():
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
You shouldn't subclass QThread. You should have a worker sent on the thread you created.
Have a look at this link to get best practices regarding threading in Qt:
http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
The example is in C++, but can be easily translated to Python.
Good luck!

Categories

Resources