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

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)

Related

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

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)

Why are my QThreads consistently crashing Maya?

I have a UI that I am wanting to use threading with inside of Maya. The reason for doing this is so I can run Maya.cmds without hanging/freezing the UI while updating the UI with progress bars, etc.
I have read a few examples from StackOverflow but my code is crashing every second time I run it. Examples I have followed are here and here
import maya.cmds as cmds
from PySide2 import QtWidgets, QtCore, QtGui, QtUiTools
import mainWindow #Main window just grabs the Maya main window and returns the object to use as parent.
class Tool(QtWidgets.QMainWindow):
def __init__(self, parent=mainWindow.getMayaMainWindow()):
super(Tool, self).__init__(parent)
UI = "pathToUI/UI.ui"
loader = QtUiTools.QUiLoader()
ui_file = QtCore.QFile(UI)
ui_file.open(QtCore.QFile.ReadOnly)
self.ui = loader.load(ui_file, self)
#Scans all window objects and if one is open with the same name as this tool then close it so we don't have two open.
mainWindow.closeUI("Tool")
###HERE'S WHERE THE THREADING STARTS###
#Create a thread
thread = QtCore.QThread()
#Create worker object
self.worker = Worker()
#Move worker object into thread (This creates an automatic queue if multiples of the same worker are called)
self.worker.moveToThread(thread)
#Connect buttons in the UI to trigger a method inside the worker which should run in a thread
self.ui.first_btn.clicked.connect(self.worker.do_something)
self.ui.second_btn.clicked.connect(self.worker.do_something_else)
self.ui.third_btn.clicked.connect(self.worker.and_so_fourth)
#Start the thread
thread.start()
#Show UI
self.ui.show()
class Worker(QtCore.QObject):
def __init__(self):
super(Worker, self).__init__() #This will immediately crash Maya on tool launch
#super(Worker).__init__() #This works to open the window but still gets an error '# TypeError: super() takes at least 1 argument (0 given)'
def do_something(self):
#Start long code here and update progress bar as needed in a still active UI.
myTool.ui.progressBar.setValue(0)
print "doing something!"
myTool.ui.progressBar.setValue(100)
def do_something_else(self):
#Start long code here and update progress bar as needed in a still active UI.
myTool.ui.progressBar.setValue(0)
print "doing something else!"
myTool.ui.progressBar.setValue(100)
def and_so_fourth(self):
#Start long code here and update progress bar as needed in a still active UI.
myTool.ui.progressBar.setValue(0)
print "and so fourth, all in the new thread in a queue of which method was called first!"
myTool.ui.progressBar.setValue(100)
#A Button inside Maya will import this code and run the 'launch' function to setup the tool
def launch():
global myTool
myTool = Tool()
I'm expecting the UI to stay active (not locked up) and the threads to be running Maya cmds without crashing Maya entirely while updating the UIs progress bars.
Any insight on this would be amazing!
From what I see it has the following errors:
thread is a local variable that is deleted when the constructor is finished executing causing what is executed to be done in the main thread which is not desired, the solution is to extend the life cycle and for this there are several solutions: 1) make class attribute, 2) pass a parent to the cycle of life they are managed by the parent. In this case use the second solution.
You should not modify the GUI from another thread, in your case you have modified the progressBar from another thread, in Qt you must use signals.
You must use the #Slot decorator in the methods that are executed in another thread.
You indicate that you want to modify myTool but you have not declared it, soglobal myTool will not work by making myTool a local variable to be deleted. The solution is to declare myTool:myTool = None
Considering the above, the solution is:
import maya.cmds as cmds
from PySide2 import QtWidgets, QtCore, QtGui, QtUiTools
import mainWindow # Main window just grabs the Maya main window and returns the object to use as parent.
class Tool(QtWidgets.QMainWindow):
def __init__(self, parent=mainWindow.getMayaMainWindow()):
super(Tool, self).__init__(parent)
UI = "pathToUI/UI.ui"
loader = QtUiTools.QUiLoader()
ui_file = QtCore.QFile(UI)
ui_file.open(QtCore.QFile.ReadOnly)
self.ui = loader.load(ui_file, self)
# Scans all window objects and if one is open with the same name as this tool then close it so we don't have two open.
mainWindow.closeUI("Tool")
# Create a thread
thread = QtCore.QThread(self)
# Create worker object
self.worker = Worker()
# Move worker object into thread (This creates an automatic queue if multiples of the same worker are called)
self.worker.moveToThread(thread)
# Connect buttons in the UI to trigger a method inside the worker which should run in a thread
self.ui.first_btn.clicked.connect(self.worker.do_something)
self.ui.second_btn.clicked.connect(self.worker.do_something_else)
self.ui.third_btn.clicked.connect(self.worker.and_so_fourth)
self.worker.valueChanged.connect(self.ui.progressBar.setValue)
# Start the thread
thread.start()
# Show UI
self.ui.show()
class Worker(QtCore.QObject):
valueChanged = QtCore.Signal(int)
#QtCore.Slot()
def do_something(self):
# Start long code here and update progress bar as needed in a still active UI.
self.valueChanged.emit(0)
print "doing something!"
self.valueChanged.emit(100)
#QtCore.Slot()
def do_something_else(self):
# Start long code here and update progress bar as needed in a still active UI.
self.valueChanged.emit(0)
print "doing something else!"
self.valueChanged.emit(100)
#QtCore.Slot()
def and_so_fourth(self):
# Start long code here and update progress bar as needed in a still active UI.
self.valueChanged.emit(0)
print "and so fourth, all in the new thread in a queue of which method was called first!"
self.valueChanged.emit(100)
myTool = None
def launch():
global myTool
myTool = Tool()

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 display progress without multi-threading

Let's say I have a PyQt program that goes through a given directory, looks for *JPEG images, and does some processing every time it finds one. Depending on the size of the selected directory, this may take from some seconds to minutes.
I would like to keep my user updated with the status - preferably with something like "x files processed out of y files" . If not, a simple running pulse progress bar by setting progressbar.setRange(0,0) works too.
From my understanding, in order to prevent my GUI from freezing, I will need a seperate thread that process the images, and the original thread that updates the GUI every interval.
But I am wondering if there is any possible way for me to do both in the same thread?
Yes, you can easily do this using processEvents, which is provided for this exact purpose.
I have used this technique for implementing a simple find-in-files dialog box. All you need to do is launch the function that processes the files with a single-shot timer, and then periodically call processEvents in the loop. This is should be good enough to update a counter with the number of files processed, and also allow the user to cancel the process, if necessary.
The only real issue is deciding on how frequently to call processEvents. The more often you call it, the more responsive the GUI will be - but this comes at the cost of considerably slowing the processing of the files. So you may have to experiment a little bit in order to find an acceptable compromise.
UPDATE:
Here's a simple demo that shows how the code could be structured:
import sys, time
from PyQt5 import QtWidgets, QtCore
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Start')
self.progress = QtWidgets.QLabel('0')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.progress)
self.button.clicked.connect(self.test)
self._stop = False
self._stopped = True
def test(self):
if self._stopped:
self._stop = False
self.progress.setText('0')
self.button.setText('Stop')
QtCore.QTimer.singleShot(1, self.process)
else:
self._stop = True
def process(self):
self._stopped = False
for index in range(1, 1000):
time.sleep(0.01)
self.progress.setText(str(index))
if not index % 20:
QtWidgets.qApp.processEvents(
QtCore.QEventLoop.AllEvents, 50)
if self._stop:
break
self._stopped = True
self.button.setText('Start')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I could not achieve the thing you need without multi threading and this is not possible because gui can be only updated in main thread. Below is an algorithm how I did this with multithreading.
Let's say you have your application processing images. Then there are the following threads:
Main thread (that blocks by GUI/QApplication-derived classes.exec())
Timer with, for example, 1 second interval which updates a variable and calls a slot in GUI thread which updates a variable in user interface.
A thread which is processing images on your pc.
def process(self):
self._status = "processing image 1"
....
def _update(self):
self.status_label.setText(self._status)
def start_processing(self, image_path):
# create thread for process and run it
# create thread for updating by using QtCore.QTimer()
# connect qtimer triggered signal to and `self._update()` slot
# connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
#pyqtSlot()
def _stop_timer():
self._qtimer.stop()
self._qtimer = None
_update_thread.finished.connect(_stop_timer)
In pyqt5 it is possible to assign a pyqtvariable from a one nested thread(first level). So you can make your variable a pyqtvariable with setter and getter and update gui in a setter or think how you can do this by yourself.
You could just use the python threading module and emit a signal in your threaded routine.
Here's a working example
from PyQt4 import QtGui, QtCore
import threading
import time
class MyWidget(QtGui.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.computeButton = QtGui.QPushButton("Compute", self)
self.progressBar = QtGui.QProgressBar()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.computeButton)
layout.addWidget(self.progressBar)
self.computeButton.clicked.connect(self.compute)
self.valueChanged.connect(self.progressBar.setValue)
def compute(self):
nbFiles = 10
self.progressBar.setRange(0, nbFiles)
def inner():
for i in range(1, nbFiles+1):
time.sleep(0.5) # Process Image
self.valueChanged.emit(i) # Notify progress
self.thread = threading.Thread(target = inner)
self.thread.start()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())

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