Anum Sheraz
Anum Sheraz

Reputation: 2625

cannot pass data between thread and Qt object in Python

I have created a GUI, on which i have to pass string data coming from serial COM port. One individual thread is handling the Serial data whereas Qt object needs to take that data and display it via 'setPlainText' method. It gives an error (on line i've marked in comments) is

"QObject: Cannot create children for a parent that is in a different thread. (Parent is QTextDocument(0x3ebaa68), parent's thread is QThread(0x3dd5c58), current thread is QThread(0x3fbd6a8)"

Heres my code;

import sys
from PyQt4 import QtGui
from My_GUI_code import Ui_Dialog
import serial # import Serial Library
import threading
import time



test=""
arduinoData = serial.Serial('COM2', 9600) #

index=0
incoming_data=""
device_0_V=""


class Serial_read(threading.Thread):
    """
    Thread to read data coming from Arduino
    """
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global incoming_data
        global device_0_V
        global test
        while 1:
           while (arduinoData.inWaiting()==0): #Wait here until there is data
             pass #do nothing
           incoming_data = arduinoData.readline() #read the line of text from the serial port
           if "V0" in incoming_data:
              index = incoming_data.index("V0=") 
              device_0_V=incoming_data[index+3:index+6]
              print device_0_V
           #print incoming_data,

class Editor(QtGui.QMainWindow, threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        global device_0_V
        super(Editor, self).__init__()
        self.ui=Ui_Dialog()

        #test=self.ui.Dev_1_V
        self.ui.setupUi(self)
        self.setWindowIcon(QtGui.QIcon('ICON.png')) 
        self.ui.Dev_1_V.setPlainText("anum")
        self.show()
        self.ui.Dev_1_ON.clicked.connect(self.handleButton)

    def run(self):
        global device_0_V
        while 1:
            self.ui.Dev_1_V.setPlainText(device_0_V)   #<<here it gives ERROR
            time.sleep(1)

    def handleButton(self):
        time = self.ui.time_dev_1.value()
        self.ui.Dev_1_V.setPlainText(device_0_V)
        print time

        #print ('Hello World')

def main():
    tx_socket_thread2 = Serial_read()
    tx_socket_thread2.start()
    app = QtGui.QApplication(sys.argv)
    ex = Editor()
    ex.start()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

I've seen some relevant questions asked in Stackoverflow, but I am not able to understand the concept still, as I am new in Classes, Qt and OOP things. I know i am doing some basic mistake here... Any help will be highly appreciated.

Upvotes: 0

Views: 1922

Answers (2)

Brendan Abel
Brendan Abel

Reputation: 37499

Here's a page describing what Qt objects are and are not thread-safe -- http://doc.qt.io/qt-4.8/threads-reentrancy.html

For the most part, GUI objects are not thread safe and you should avoid modifying them from other threads.

One way of affecting the GUI from other threads is to use the signal and slot system, which is safe to use between threads so long as any objects passed are thread-safe. This usually means creating a thread-safe data structure in the secondary thread and passing it along with a signal to the main thread, which then reads the data structure and updates the GUI.

A more advanced version of that design pattern is to use a 2-way queue. One queue is populated by the main thread, which creates worker threads that process the items in the queue. When finished, the worker threads populate the other queue with thread-safe return values that the main thread then processes. Signals and events are still used to notify the main and worker threads when there are items in the queue to process.

Also, unless absolutely want to directly manage the threads, you can use QRunnable and QThreadPool to kick off threads without the need to directly manage them.

Upvotes: 0

Anum Sheraz
Anum Sheraz

Reputation: 2625

So after some readings on related asked questions in Stack overflow, I've managed to achieve what I want, Heres the code;

import sys
from PyQt4 import QtGui, QtCore
from My_GUI_code import Ui_Dialog
import serial # import Serial Library
import threading
import time



test=""
arduinoData = serial.Serial('COM2', 9600) #

index=0
incoming_data=""
device_0_V=""


class Serial_read(threading.Thread):
    """
    Thread to read data coming from Arduino
    """
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global incoming_data
        global device_0_V
        global test
        while 1:
           while (arduinoData.inWaiting()==0): #Wait here until there is data
             pass #do nothing
           incoming_data = arduinoData.readline() #read the line of text from the serial port
           if "V0" in incoming_data:
              index = incoming_data.index("V0=") 
              device_0_V=incoming_data[index+3:index+6]
              print device_0_V
           #print incoming_data,

class Editor(QtGui.QMainWindow):

    def __init__(self):
        #threading.Thread.__init__(self)
        global device_0_V
        super(Editor, self).__init__()
        self.ui=Ui_Dialog()

        #test=self.ui.Dev_1_V
        self.ui.setupUi(self)
        self.setWindowIcon(QtGui.QIcon('ICON.png')) 
        self.ui.Dev_1_V.setPlainText("anum")
        self.show()
        self.ui.Dev_1_ON.clicked.connect(self.handleButton)

        self.worker = Worker(self)  # an independent thread that will listen to a signal 'beep' and trigger a function self.update
        self.connect(self.worker, QtCore.SIGNAL('beep'), self.update) 
        self.worker.start()  # start the thread

    def update(self, Serial_data):
        # here, I am getting the Serial data via signaling 
        if "V0" in incoming_data:
              index = incoming_data.index("V0=") 
              device_0_V=incoming_data[index+3:index+7]
              self.ui.Dev_1_V.setPlainText(device_0_V)


    def handleButton(self):
        time = self.ui.time_dev_1.value()
        self.ui.Dev_1_V.setPlainText(device_0_V)
        print time

        #print ('Hello World')

class Worker(QtCore.QThread):
    def __init__(self, host_window):
        super(Worker, self).__init__()
        self.running = False

    def run(self):
        self.running = True
        global incoming_data    #kept the Serial data global
        global device_0_V
        while self.running:
              #sending 'beep' signal to the main Qt object, with string data 'incoming_data'
              self.emit(QtCore.SIGNAL('beep'), incoming_data) 
              time.sleep(0.1)

    def stop(self):
        self.running = False


def main():
    tx_socket_thread2 = Serial_read()
    tx_socket_thread2.start()

    app = QtGui.QApplication(sys.argv)
    ex = Editor()

    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

Inside the main Qt object, I've created a QThread "Worker" that sends signals to main Qt object with the Serial data. The update function is triggered every time a signal arrives from worker thread, and then further reads the data coming from worker thread.

Got help from this question

Thank you @Andy and @SiHa for your participation

Upvotes: 1

Related Questions