When you're working with a QTableView and a custom model, it's common to want to highlight entire rows based on some condition in your data. For example, you might want to color a row blue when a device has a "NewConnection" status, or red when something has gone wrong.
Understanding How data() Works
In Qt's Model/View architecture, the view calls your model's data() method for every cell in the table — and for each cell, it asks about multiple roles. One of those roles is Qt.BackgroundRole, which tells the view what background color to use for that cell.
The view asks for Qt.BackgroundRole on every single cell, not just one column. So if your data() method returns a color for Qt.BackgroundRole based on the row data (ignoring the column), the color will be applied to every cell in that row.
Let's build a working example.
A Complete Working Example
Here's a full example you can run directly. It creates a QTableView with colored rows based on the PRESENT_STATUS field in each row of data:
import sys
from typing import Union
from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView
class TableModel(QAbstractTableModel):
def __init__(self, data: Union[list, None] = None):
super().__init__()
self._data = data or []
self._hdr = self._gen_hdr_data() if data else []
self._base_color = {
"NewConnection": QColor("blue"),
"Registered": QColor("green"),
}
def _gen_hdr_data(self):
"""Build a sorted list of all unique keys across all row dicts."""
all_keys = set()
for d in self._data:
all_keys.update(d.keys())
return sorted(all_keys)
def rowCount(self, parent=QModelIndex()):
return len(self._data)
def columnCount(self, parent=QModelIndex()):
return len(self._hdr)
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self._hdr[section]
def data(self, index: QModelIndex, role: int):
if not index.isValid():
return None
row_dict = self._data[index.row()]
state = row_dict.get("PRESENT_STATUS", "")
if role == Qt.DisplayRole:
col_key = self._hdr[index.column()]
value = row_dict.get(col_key, "")
return str(value) if value else ""
if role == Qt.BackgroundRole:
color = self._base_color.get(state)
if color:
return color
return None
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Row Background Colors in QTableView")
data = [
{"IP": "192.168.1.10", "PRESENT_STATUS": "NewConnection"},
{"IP": "192.168.1.108", "FORMER_STATUS": "NewConnection",
"PRESENT_STATUS": "Registered"},
{"IP": "192.168.1.50", "PRESENT_STATUS": "Unknown"},
]
self.table = QTableView()
model = TableModel(data)
self.table.setModel(model)
self.setCentralWidget(self.table)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The method that Qt calls on the model is called data, so in the example above, the list is stored as self._data (with a leading underscore) to avoid this.
Run this and you'll see three rows. The first row ("NewConnection") has a blue background, the second row ("Registered") has a green background, and the third row ("Unknown") has no special coloring because it isn't in the _base_color dictionary.

Why This Colors the Whole Row
Let's zoom in on the Qt.BackgroundRole section of data():
if role == Qt.BackgroundRole:
color = self._base_color.get(state)
if color:
return color
Notice that index.column() isn't used here at all. The color decision is based entirely on the row's PRESENT_STATUS value. Since the view calls data() for every cell in the row — column 0, column 1, column 2, etc. — and each call gets the same color back, the entire row ends up painted.
If you only wanted to color a specific column (say, just the status column), you would add a column check:
if role == Qt.BackgroundRole:
# Only color the PRESENT_STATUS column
if self._hdr[index.column()] == "PRESENT_STATUS":
color = self._base_color.get(state)
if color:
return color
But for row-level coloring, leaving out the column check is exactly what you want.
Making the Text Readable
One thing you'll notice with a dark background color like blue is that the default black text becomes hard to read. You can fix this by also handling Qt.ForegroundRole and returning a light text color when the background is dark:
def data(self, index: QModelIndex, role: int):
if not index.isValid():
return None
row_dict = self._data[index.row()]
state = row_dict.get("PRESENT_STATUS", "")
if role == Qt.DisplayRole:
col_key = self._hdr[index.column()]
value = row_dict.get(col_key, "")
return str(value) if value else ""
if role == Qt.BackgroundRole:
color = self._base_color.get(state)
if color:
return color
if role == Qt.ForegroundRole:
# If this row has a background color, use white text.
if state in self._base_color:
return QColor("white")
return None
Now blue and green rows will have white text, making everything easy to read.
Updating Colors Dynamically
If your data changes at runtime — for example, a device's status changes from "NewConnection" to "Registered" — you need to tell the view that something has changed so it repaints. You do this by emitting the dataChanged signal:
def update_status(self, row, new_status):
self._data[row]["PRESENT_STATUS"] = new_status
# Emit dataChanged for the entire row.
top_left = self.index(row, 0)
bottom_right = self.index(row, self.columnCount() - 1)
self.dataChanged.emit(top_left, bottom_right)
This tells the view to re-query data() for every cell in that row, which picks up both the new display text and the new background color. For a deeper look at how signals work to keep your model and view in sync, see Signals, Slots & Events.
Summary
Coloring entire rows in a QTableView is straightforward once you understand how the model's data() method works. The view asks for each role on every cell, so returning a color from Qt.BackgroundRole based on row-level data — without filtering by column — naturally paints the whole row. Pair that with Qt.ForegroundRole for readable text, and you've got a clean, data-driven way to highlight rows in your table.
To learn more about using QTableView with custom models and data from numpy or pandas, see the QTableView with numpy and pandas tutorial. If you want to add sorting and filtering to your table, take a look at Sorting and Filtering Tables.
Packaging Python Applications with PyInstaller by Martin Fitzpatrick
This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.