我对QTableView有以下疑问,我添加了一些代码,这些数据会根据数据帧在最后一列中包含的字符串来更改行背景。

    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


当表第一次加载时,它正确地绘制了表,但事实是,我有一段特定的代码始终每5秒运行一次,以便用新鲜的信息刷新表。

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)


问题是,当上面的代码运行时,该表实际上会更新数据,但不会更改颜色。我目前尝试过其他方法,例如
定义setData并在那里更新颜色,但无济于事。现在,我问您是否有人对更新QTableView上的颜色有所了解。

顺便说一下,我将为下面的python程序附加整个代码以提供上下文。

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_())

最佳答案

我了解您正在使用threading.Timer(),因为加载和处理数据帧的过程非常耗时,并且您想执行定期任务(如果该任务不占用大量时间,那么另一种选择是使用QTimer),但是问题是您正在创建模型并从另一个线程(它被Qt禁止,如the docs所指示)添加可视信息,该信息是GUI的一部分。

考虑到上述情况,最好通过信号将辅助线程的信息发送到主线程,我还实现了一种重置模型信息的方法,而无需创建新模型,最后我添加了验证代码,因此该代码不会失败。

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)


如您所见,我为测试随机创建了数据帧,但如果要使用代码,则必须按如下所示替换它:

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()

10-08 05:41
查看更多