Domanda

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 QObjects 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 :

  • OS : Windows 7 64-bits
  • Python version : 3.3
  • PySide version : 1.2.1
È stato utile?

Soluzione

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top