Reputation: 583
I'm trying to build an application in Python with PySide that has several windows. Each window executes some functions and the execution of one function should not stop the other windows from executing their own functions and this is why I need to use multi-threading. However I have no idea how to do so. I'm trying to use the new method to use threads (the correct one) but all examples I found online are in C++ and I have no knowledge of C++ and this is why I'm asking for help.
For simplification purposes, I have constructed two modules, one named test_main
and the other named test_wdw
This is the code for test_main
:
import sys
import test_wdw
from PySide import QtGui
from PySide import QtCore
class Main_Window(QtGui.QMainWindow):
def __init__(self):
super(Main_Window,self).__init__()
self.initUI()
def initUI(self):
self.statusBar()
self.new_window=QtGui.QAction("&Window alpha",self)
self.new_window.triggered.connect(self.open_window)
self.menu_bar=self.menuBar()
self.menu1=self.menu_bar.addMenu('&Menu 1')
self.menu1.addAction(self.new_window)
# Creates a QMdiArea to manage all windows
self.wmanager=QtGui.QMdiArea()
self.setCentralWidget(self.wmanager)
# Shows the main window
self.showMaximized()
def open_window(self):
test_wdw.launch_window()
test_wdw.window_alpha=self.wmanager.addSubWindow(test_wdw.window)
# Shows the new window
test_wdw.window_alpha.show()
def main():
app=QtGui.QApplication(sys.argv)
main_wdw=Main_Window()
sys.exit(app.exec_())
if __name__=="__main__":
main()
This is the code for test_wdw
:
from PySide import QtGui
from PySide import QtCore
def launch_window():
global window
window=QtGui.QWidget()
window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
window.setWindowTitle('Window 1')
window.grid=QtGui.QGridLayout()
window.label=QtGui.QLabel("Hello")
window.grid.addWidget(window.label,0,0)
window.setLayout(window.grid)
window.setFixedSize(window.sizeHint())
class running_operation(QtCore.QObject):
# Function I would like to run in a separate thread
def run_operation(self):
while True:
print("hi")
myThread=QtCore.QThread()
operations = running_operation()
operations.moveToThread(myThread)
myThread.start()
My problem is I don't know where to go from here. I want that once the new window is launched it starts the run_operation()
function in the new thread.
I tried to add operations.run_operation()
to the end of the test_wdw
file but what happened is that run_operation()
started executing immediately after running the application and no GUI was displayed at all.
Adding operations.run_operation()
inside the launch_window function just crashed the GUI.
This led me to realize that the run_operation
function wasn't running in a separate thread as expected.
I continued to read through the documentation and I believe that I need to create signals and slots for it to run. I know how to use signal and slots to connect QObject
s to functions I expect them to execute when triggered but I don't understand what does signals and slots have to do with a QThread
and I'm unable to achieve the expected result.
I would like to have an explanation of the role of signals and slots in the QThread
context and how they are used and I would like to have a correction of the code I posted so it runs as expected.
Additional information :
Upvotes: 1
Views: 316
Reputation: 583
After several trials and errors and decoding of some C++ tutorials, I ended up by finding the solution myself.
The code for test_main
remains the same.
import sys
import test_wdw
from PySide import QtGui
from PySide import QtCore
class Main_Window(QtGui.QMainWindow):
def __init__(self):
super(Main_Window,self).__init__()
self.initUI()
def initUI(self):
self.statusBar()
self.new_window=QtGui.QAction("&Window alpha",self)
self.new_window.triggered.connect(self.open_window)
self.menu_bar=self.menuBar()
self.menu1=self.menu_bar.addMenu('&Menu 1')
self.menu1.addAction(self.new_window)
# Creates a QMdiArea to manage all windows
self.wmanager=QtGui.QMdiArea()
self.setCentralWidget(self.wmanager)
# Shows the main window
self.showMaximized()
def open_window(self):
test_wdw.launch_window()
test_wdw.window_alpha=self.wmanager.addSubWindow(test_wdw.window)
# Shows the new window
test_wdw.window_alpha.show()
def main():
app=QtGui.QApplication(sys.argv)
main_wdw=Main_Window()
sys.exit(app.exec_())
if __name__=="__main__":
main()
The correct code for test_wdw
is :
from PySide import QtGui
from PySide import QtCore
def launch_window():
global window
# Creates a new window
window=QtGui.QWidget()
window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
window.setWindowTitle('Window 1')
# Creates a layout for the window and populates it with a Qlabel
window.grid=QtGui.QGridLayout()
window.label=QtGui.QLabel("Hello")
window.grid.addWidget(window.label,0,0)
# Sets the layout to the window and sets the size of the window
window.setLayout(window.grid)
window.setFixedSize(window.sizeHint())
# Starts the thread
myThread.start()
class running_operation(QtCore.QObject):
# Creates a QtCore Signal for when the operation is over (if you want something to happen when the function ends processing)
finish_operation=QtCore.Signal()
# Wraps the function to run inside a QtCore Slot (MANDATORY !)
@QtCore.Slot()
# Function I wanted to run in a separate thread ! NOW IT RUNS !
def run_operation(self):
global counter
counter=0
# Setting a timer that fires every second and calling at the same time the function print_counter()
global timer
timer=QtCore.QTimer(self)
timer.timeout.connect(self.print_counter)
timer.start(1000)
def print_counter(self):
global counter
global timer
# A random function to execute just for testing purposes, the counter keeps counting up every second.
if counter <= 3 :
counter += 1
print(counter)
elif counter == 4 :
# At seconds, we emit the finish_operation signal.
counter += 1
self.finish_operation.emit()
# At 5 seconds and more, the counter keeps counting up again every second (just to check if the thread actually exited)
else:
counter += 1
print(counter)
# Creates a new thread
myThread=QtCore.QThread()
# Creates the object "operations"
operations = running_operation()
# Moves the object to the thread
operations.moveToThread(myThread)
# Connects the QThread.started signal function to the run_operation() function.
myThread.started.connect(operations.run_operation)
# Connects the finish_operation signal to quitting the thread.
operations.finish_operation.connect(myThread.quit)
Everything I added to the original post (the question) is explained in the code comments.
I can tell the function started in a separate thread because the GUI didn't hang up.
I can tell the thread quit correctly because after 4 seconds, the program prints nothing to the output. If the thread didn't quit correctly, and due to the way I constructed the function print_counter()
, the program would have stopped printing to the output at second 4 for 1 second, and then it would have started printing again to the output at the 5th second.
I hope some people will find this useful and can build on it.
Upvotes: 2