Issue
I would like to change the background color of specific index on my table, but only after a specific task is completed.
I know that I can use the Background role to change the color in my Table model, but I want to change the background color on external factors and not based on changes to the table itself. For example, the code below shows a basic example of a QTableView with 6 rows displayed in a QWidget. Inside the main app I am able to change the text of specific indexes using setData as seen below.
model.setData(model.index(2, 0), "Task Complete")
Here is the full code:
import sys
from PySide6.QtWidgets import (
QApplication, QWidget, QTableView, QVBoxLayout
)
from PySide6.QtCore import Qt, QAbstractTableModel
from PySide6.QtGui import QBrush
class TableModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index, role=Qt.DisplayRole):
# display data
if role == Qt.DisplayRole:
try:
return self._data[index.row()][index.column()]
except IndexError:
return ''
def setData(self, index, value, role=Qt.EditRole):
if role in (Qt.DisplayRole, Qt.EditRole):
# if value is blank
if not value:
return False
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
def flags(self, index):
return super().flags(index) | Qt.ItemIsEditable
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.window_width, self.window_height = 200, 250
self.setMinimumSize(self.window_width, self.window_height)
self.layout = {}
self.layout['main'] = QVBoxLayout()
self.setLayout(self.layout['main'])
self.table = QTableView()
self.layout['main'].addWidget(self.table)
model = TableModel(data)
self.table.setModel(model)
# THIS IS WHERE THE QUESTION IS
model.setData(model.index(2, 0), "Task Complete") # Change background color instead of text
model.setData(model.index(5, 0), "Task Complete") # Change background color instead of text
if __name__ == '__main__':
data = [
["Task 1"],
["Task 2"],
["Task 3"],
["Task 4"],
["Task 5"],
["Task 6"],
]
app = QApplication(sys.argv)
myApp = MainApp()
myApp.show()
try:
sys.exit(app.exec())
except SystemExit:
print('Closing Window...')
I have tried to change the setData function to use the Qt.BackgroundRole instead of Qt.EditRole, but that does not work for changing the color. The result is that the code runs, but nothing happens.
I want to be able to fill the background with whatever color I choose based on the specific index I pick. However, I want this code to reside inside the MainApp class and not in the TableModel Class.
Suggestions Tried
Added code to data()
if role == Qt.BackgroundRole:
return QBrush(Qt.green)
Changed setData()
def setData(self, index, value, role=Qt.BackgroundRole):
if role in (Qt.DisplayRole, Qt.BackgroundRole):
# if value is blank
if not value:
return False
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
Changed setData in MainApp too
model.setData(model.index(5, 0), QBrush(Qt.green))
This resulted in highlighting the entire table in green instead of specific index.
Solution
If you want to set different colors for each index, you must store the color information in another data structure and return the corresponding value for the index.
Both data()
and setData()
must access different values depending on the role (see the documentation about item roles), meaning that you must not use self._data
indiscriminately for anything role. If you set the color for a row/column in the same data structure you use for the text, then the text is lost.
A simple solution is to create a list of lists that has the same size of the source data, using None
as default value.
class TableModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
rows = len(data)
cols = len(data[0])
self._backgrounds = [[None] * cols for _ in range(rows)]
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return
elif role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
elif role == Qt.BackgroundRole:
return self._backgrounds[index.row()][index.column()]
def setData(self, index, value, role=Qt.EditRole):
if (
not index.isValid()
or index.row() >= len(self._data)
or index.column() >= len(self._data[0])
):
return False
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
elif role == Qt.BackgroundRole:
self._backgrounds[index.row()][index.column()] = value
else:
return False
self.dataChanged.emit(index, index, [role])
return True
Note: you should always ensure that data
has at least one row, otherwise columnCount()
will raise an exception.
Then, to update the color, you must also use the proper role:
model.setData(model.index(5, 0), QBrush(Qt.green), Qt.BackgroundRole)
Note that if you don't need to keep the data structure intact (containing only the displayed values), a common solution is to use dictionaries.
You could use common dictionary that has the role as key and the data structure as value:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
rows = len(data)
cols = len(data[0])
self._data = {
Qt.DisplayRole: data,
Qt.BackgroundRole: [[None] * cols for _ in range(rows)]
}
# implement the other functions accordingly
Otherwise, use a single structure that uses unique dictionaries for each item:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = []
for rowData in data:
self._data.append([
{Qt.DisplayRole: item} for item in rowData
])
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return
data = self._data[index.row()][index.column()]
if role == Qt.EditRole:
role = Qt.DisplayRole
return data.get(role)
def setData(self, index, value, role=Qt.EditRole):
if (
not index.isValid()
or role not in (Qt.EditRole, Qt.BackgroundRole)
):
return False
self._data[index.row()][index.column()][role] = value
self.dataChanged.emit(index, index, [role])
return True
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.