问题
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