How to make QTableView refresh Background color after it loads data again

空扰寡人 提交于 2021-02-18 19:12:49

问题


i have the following doubt regarding QTableView, i have added some code that changes the row background depending on what string the dataframe contains on the last column.

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.BackgroundRole:
                if df.iloc[index.row(),5] == "Ready for QC":
                    return QBrush(Qt.yellow)
                if df.iloc[index.row(),5] == "In Progress":
                    return QBrush(Qt.green)
            if role == Qt.DisplayRole:
                return str(self._data.iloc[index.row(), index.column()])
        return None

When the table loads the first time it properly paints the Table, the thing is that i have a specific part of the code that always runs every 5 seconds as to refresh the Table with fresh information.

def printit():
    threading.Timer(5.0, printit).start()
    weekNumber = date.today().isocalendar()[1]
    aux = pd.read_excel('PCS tasks 2020.xlsm',sheet_name='W'+str(weekNumber))
    today = datetime.today()
    df = aux[aux['Date Received'] == today.strftime("%Y-%d-%m")]
    df = df[["Requestor","Subject","Task type","Created by","QC Executive","Status"]].fillna("")
    df = df[df['Status'] != "Completed"]
    model = pandasModel(df)
    view.setModel(None)
    view.setModel(model)

The thing is that when the above code runs, the table does in fact update the data, but it does not change the colors. I currently have tried different methods like defining setData and in there update the colors but with no avail. Now im asking you if someone knows something regarding updating colors on a QTableView.

By the way, I'm attaching the entire code for the python program below as to give context.

import sys
import pandas as pd
from PyQt5.QtWidgets import QApplication, QTableView
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtGui import QBrush
from datetime import date, datetime
import threading

weekNumber = date.today().isocalendar()[1]
aux = pd.read_excel('PCS tasks 2020.xlsm',sheet_name='W'+str(weekNumber))
today = datetime.today()
df = aux[aux['Date Received'] == today.strftime("%Y-%d-%m")]
df = df[["Requestor","Subject","Task type","Created by","QC Executive","Status"]].fillna("")
df = df[df['Status'] != "Completed"]

def printit():
    threading.Timer(5.0, printit).start()
    weekNumber = date.today().isocalendar()[1]
    aux = pd.read_excel('PCS tasks 2020.xlsm',sheet_name='W'+str(weekNumber))
    today = datetime.today()
    df = aux[aux['Date Received'] == today.strftime("%Y-%d-%m")]
    df = df[["Requestor","Subject","Task type","Created by","QC Executive","Status"]].fillna("")
    df = df[df['Status'] != "Completed"]
    model = pandasModel(df)
    view.setModel(None)
    view.setModel(model)

class pandasModel(QAbstractTableModel):

    def __init__(self, data):
        QAbstractTableModel.__init__(self)
        self._data = data

    def rowCount(self, parent=None):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1] -1

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.BackgroundRole:
                if df.iloc[index.row(),5] == "Ready for QC":
                    return QBrush(Qt.yellow)
                if df.iloc[index.row(),5] == "In Progress":
                    return QBrush(Qt.green)
            if role == Qt.DisplayRole:
                return str(self._data.iloc[index.row(), index.column()])
        return None

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[col]
        return None

if __name__ == '__main__':
    app = QApplication(sys.argv)
    model = pandasModel(df)
    view = QTableView()
    view.setModel(model)
    view.resize(523, 300)
    printit()
    view.show()
    sys.exit(app.exec_())

回答1:


I understand that you are using threading.Timer() because the process of loading and processing the dataframe is very time consuming and you want to do a periodic task (if the task does not consume much time then another option is to use QTimer) but the problem is that you are creating a model and adding information in sight that is part of the GUI from another thread which is prohibited by Qt as indicated by the docs.

Considering the above it is better to send the information of the secondary thread to the main thread through signals, I have also implemented a method that resets the information of the model avoiding the need to create new models, and finally I have added verification code so that the code don't fail.

import sys
import pandas as pd

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QAbstractTableModel, QObject, Qt
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import QApplication, QTableView

import threading


class PandasManager(QObject):
    dataFrameChanged = pyqtSignal(pd.DataFrame)

    def start(self):
        self.t = threading.Timer(0, self.load)
        self.t.start()

    def load(self):
        import random

        headers = list("ABCDEFG")
        data = [random.sample(range(255), len(headers)) for _ in headers]

        for d in data:
            d[5] = random.choice(["Ready for QC", "In Progress", "Another option"])

        df = pd.DataFrame(data, columns=headers,)

        self.dataFrameChanged.emit(df)
        self.t = threading.Timer(5.0, self.load)
        self.t.start()

    def stop(self):
        self.t.cancel()


class PandasModel(QAbstractTableModel):
    def __init__(self, df=pd.DataFrame()):
        QAbstractTableModel.__init__(self)
        self._df = df

    @pyqtSlot(pd.DataFrame)
    def setDataFrame(self, df):
        self.beginResetModel()
        self._df = df
        self.endResetModel()

    def rowCount(self, parent=None):
        return self._df.shape[0]

    def columnCount(self, parent=None):
        return self._df.shape[1]

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.BackgroundRole:
                if self.columnCount() >= 6:
                    it = self._df.iloc[index.row(), 5]
                    if it == "Ready for QC":
                        return QBrush(Qt.yellow)
                    if it == "In Progress":
                        return QBrush(Qt.green)
            if role == Qt.DisplayRole:
                return str(self._df.iloc[index.row(), index.column()])

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._df.columns[col]
        return None


if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QTableView()
    model = PandasModel()
    w.setModel(model)
    w.show()

    manager = PandasManager()
    manager.dataFrameChanged.connect(model.setDataFrame)
    manager.start()

    ret = app.exec_()

    manager.stop()

    sys.exit(ret)

As you can see I created dataframes randomly for my test but if you want to use your code then you must replace it as follows:

def load(self):
    weekNumber = date.today().isocalendar()[1]
    aux = pd.read_excel("PCS tasks 2020.xlsm", sheet_name="W" + str(weekNumber))
    today = datetime.today()
    df = aux[aux["Date Received"] == today.strftime("%Y-%d-%m")]
    df = df[
        [
            "Requestor",
            "Subject",
            "Task type",
            "Created by",
            "QC Executive",
            "Status",
        ]
    ].fillna("")
    df = df[df["Status"] != "Completed"]
    self.dataFrameChanged.emit(df)
    self.t = threading.Timer(5.0, self.load)
    self.t.start()


来源:https://stackoverflow.com/questions/59669863/how-to-make-qtableview-refresh-background-color-after-it-loads-data-again

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!