mehulJ
mehulJ

Reputation: 119

How to use QThread() within class of QWidget function?

I was going through QThread() document where it is mentioned to create a new class worker and then create QThread in main function and afterwards use movingToThread

https://realpython.com/python-pyqt-qthread/#:~:text=In%20PyQt%2C%20you%20use%20QThread,the%20thread%20as%20an%20argument.

My question is I have a QMainWindow, inside that function called update_table_live which is basically updating my table widget cell value in while loop. I want this function to be a part of QThread? If I go with example of realpython then it is making me confused as my QMainWindow contains all object as self So how should I make them separate in worker class just like shown in realpython website or is there any other way to create QThread for function inside mainWindow?

Trying to make a clean version of code from original.

import utility
from PyQt5.QtWidgets import *  
from PyQt5.QtCore import Qt, QThread, QObject, pyqtSignal
import sys

class mainUI(QWidget):
    def __init__(self, parent=None):
        """ This module setup entire widgets for UI
        with setup of some basic functionality.

        Args:
            parent: None
        """
        QWidget.__init__(self, parent, Qt.WindowStaysOnTopHint)

        self.setMinimumSize(1000, 500)

        self.table_live = QTableWidget()
        self.table_live.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_live.setColumnCount(6)
        self.table_live.setHorizontalHeaderLabels(
        ['Name', 'Quantity', 'Discount Price', 'Market Price', 'Change', 'P/L']
        )

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.table_live)
        self.setLayout(self.layout)
        
        #: This Function to be in thread
        self.update_table_live()

    def update_table_live(self):
        """ This method will update live table
       on current market value.

        Returns:
            None

        """
        while True:
            market = utility.get_live_data()  #: This function get live data.
            self.table_live.setRowCount(0)
    
            for name, product_data in market.items():
                row_count = self.table_live.rowCount()
                self.table_live.insertRow(row_count)
    
                item_name = QTableWidgetItem(name)
                item_quantity = QTableWidgetItem(product_data['quantity'])
                item_discount = QTableWidgetItem(product_data['discount'])
                item_current = QTableWidgetItem(product_data['price'])
                item_change = QTableWidgetItem(product_data['change'])
    
                item_pl = QTableWidgetItem(product_data['p_l'])
    
                self.table_live.setItem(row_count, 0, item_name)
                self.table_live.setItem(row_count, 1, item_quantity)
                self.table_live.setItem(row_count, 2, item_discount)
                self.table_live.setItem(row_count, 3, item_current)
                self.table_live.setItem(row_count, 4, item_change)
                self.table_live.setItem(row_count, 5, item_pl)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = mainUI()
    win.show()
    app.exec_()

Upvotes: 1

Views: 1181

Answers (1)

eyllanesc
eyllanesc

Reputation: 243945

The main problem in your code is the while True since this in itself blocks the eventloop so you have 2 options:

Threads

NOTE: If the get_live_data() task is time consuming then you should use threads.

You have to be clear about the following:

  • Threads should only be used as a last option.
  • Threads should only execute tasks that consume a lot of time since if they are executed in the main thread, the Qt eventloop will be blocked and consequently the GUI will freeze.
  • The GUI should not be modified directly in the threads, instead you should send the information through signals so that it is updated in the main thread.

Considering the above, the solution is:

class Worker(QObject):
    dataChanged = pyqtSignal(dict)

    def task(self):
        while True:
            market = utility.get_live_data()
            self.dataChanged.emit(market)


class mainUI(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent, Qt.WindowStaysOnTopHint)

        self.setMinimumSize(1000, 500)

        self.table_live = QTableWidget()
        self.table_live.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_live.setColumnCount(6)
        self.table_live.setHorizontalHeaderLabels(
            ["Name", "Quantity", "Discount Price", "Market Price", "Change", "P/L"]
        )

        layout = QHBoxLayout(self)
        layout.addWidget(self.table_live)

        self.worker = Worker()
        thread = QThread(self)
        self.worker.moveToThread(thread)
        self.worker.dataChanged.connect(self.update_table_live)
        thread.started.connect(self.worker.task)
        thread.start()

    def update_table_live(self, market):
        for name, product_data in market.items():
            row_count = self.table_live.rowCount()
            self.table_live.insertRow(row_count)

            item_name = QTableWidgetItem(name)
            item_quantity = QTableWidgetItem(product_data["quantity"])
            item_discount = QTableWidgetItem(product_data["discount"])
            item_current = QTableWidgetItem(product_data["price"])
            item_change = QTableWidgetItem(product_data["change"])

            item_pl = QTableWidgetItem(product_data["p_l"])

            self.table_live.setItem(row_count, 0, item_name)
            self.table_live.setItem(row_count, 1, item_quantity)
            self.table_live.setItem(row_count, 2, item_discount)
            self.table_live.setItem(row_count, 3, item_current)
            self.table_live.setItem(row_count, 4, item_change)
            self.table_live.setItem(row_count, 5, item_pl)

QTimer

NOTE: If get_live_data is not very time consuming then it is better to use QTimer to avoid blocking loop.

class mainUI(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent, Qt.WindowStaysOnTopHint)

        self.setMinimumSize(1000, 500)

        self.table_live = QTableWidget()
        self.table_live.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_live.setColumnCount(6)
        self.table_live.setHorizontalHeaderLabels(
            ["Name", "Quantity", "Discount Price", "Market Price", "Change", "P/L"]
        )

        layout = QHBoxLayout(self)
        layout.addWidget(self.table_live)

        self.timer = QTimer(interval=100, timeout=self.handle_timeout)
        self.timer.start()

    def handle_timeout(self):
        market = utility.get_live_data()
        self.update_table_live(market)

    def update_table_live(self, market):
        for name, product_data in market.items():
            row_count = self.table_live.rowCount()
            self.table_live.insertRow(row_count)

            item_name = QTableWidgetItem(name)
            item_quantity = QTableWidgetItem(product_data["quantity"])
            item_discount = QTableWidgetItem(product_data["discount"])
            item_current = QTableWidgetItem(product_data["price"])
            item_change = QTableWidgetItem(product_data["change"])

            item_pl = QTableWidgetItem(product_data["p_l"])

            self.table_live.setItem(row_count, 0, item_name)
            self.table_live.setItem(row_count, 1, item_quantity)
            self.table_live.setItem(row_count, 2, item_discount)
            self.table_live.setItem(row_count, 3, item_current)
            self.table_live.setItem(row_count, 4, item_change)
            self.table_live.setItem(row_count, 5, item_pl)

Upvotes: 3

Related Questions