paint custom QTableView row colors
-
I have an application with a QTableView that displays log entries (colored by thread ID). The code below shows how did this by assigning colors to each row using a thread ID. This thread coloring is optional (defaulting to normal black text on a white background or vise-versa if dark mode is enabled), so when I toggle it, I need to repaint the QTableView rows (at least the visible ones). I think the way to optionally achieve this through the QT Model/View is to subclass of QStyledItemDelegate and do something with roles, but I am really not sure and would greatly appreciate some simple example.
void MainWindow::handleAddLogEntry( const level::level_enum aLevel, const QString& rLogEntry, const Qt::HANDLE aThreadID) const { // foreground colors for default main thread text const static auto gFGBrushDark = QBrush(QColorConstants::White); const static auto gFGBrushLight = QBrush(QColorConstants::Black); static const auto gBrushes = std::array { QBrush(QColorConstants::Red), QBrush(QColorConstants::Green), QBrush(QColorConstants::Blue), QBrush(QColorConstants::Magenta) }; // do not make static as we need to adjust for theme const auto gDefaultBrushColor = (mThemeStyle == ThemeStyle::Light) ? gFGBrushLight : gFGBrushDark; static const auto gMainThreadID = QThread::currentThreadId(); static std::map<Qt::HANDLE, QBrush> gBrushInfo; // reset default brush color each time as it is theme dependent // and the theme may have been changed since the last log entry gBrushInfo[gMainThreadID] = gDefaultBrushColor; QList<QStandardItem*> row; QStandardItem* item = nullptr; auto brushColor = gDefaultBrushColor; // lookup thread ID for coloring if (const auto it = gBrushInfo.find( aThreadID); it != gBrushInfo.cend()) { if (mColorLog) { brushColor = it->second; } } else { // update gBrushInfo with thread color static auto gNextColor = 0; // insert the new color in gBrushInfo const auto& brush = gBrushes[ gNextColor % gBrushes.size()]; gBrushInfo[aThreadID] = brush; if (mColorLog) { brushColor = brush; } ++gNextColor; } // Col1: Timestamp item = new QStandardItem(getTimeStampStr()); item->setForeground(brushColor); row.emplaceBack(item); // Col2: Thread ID - 32 bit hex integer with leading 0x item = new QStandardItem(QString("0x%1").arg( reinterpret_cast<const uint64_t>(aThreadID), 8, 16, QChar('0'))); item->setForeground(brushColor); row.emplaceBack(item); // Col3: Level item = new QStandardItem(std::string( enum_name(aLevel)).c_str()); item->setForeground(brushColor); row.emplaceBack(item); // Col4: Description. item = new QStandardItem(rLogEntry); item->setForeground(brushColor); row.emplaceBack(item); mLogViewModel->appendRow(row); }
-
Simply set the new colors when you toggle it. The view is then updated automatically. Even though using QStandardItemModel would be slow for this when there a lot of items.
-
@Christian-Ehrlicher Thanks but for some strange reason the table doesn't refresh itself when I toggle the colors. To do so I would need to erase the entire table and re-insert the entries - but I cannot as it is a log file. Whenever I turn the thread coloring on, future rows are added with the expected color, then when I turn it off the log entries following the colored entries are back to the usual black/white. I thought that the QStyledItemDelegate or some subclass thereof was the trick to automatically handle the repainting of rows.
I also tried to force an updated as follows and it did not work either.
const auto& topLeft = mLogViewModel->index(0, 0); const auto& bottomRight = mLogViewModel->index( mLogViewModel->rowCount() - 1, mLogViewModel->columnCount() - 1); Q_EMIT mLogViewModel->dataChanged(topLeft, bottomRight);
-
@johnco3
If I understand right. You have a model with existing rows. You want to change the color of those rows. So you needQTableView
to redraw those rows. So you need to emitdataChanged()
to cover all rows to tell the view they need updating. Is that your situation?EDIT
Oh, lol, I have only just noticed you have that in your final code!Well, you have already explicitly called
QStandardItem::setForeground()
on existing cells, right? So nothing is going to change that unless you do so yourself on those items? If you cannot calculate desired color to return fromdata()
, so that you do not have to set it, then you will have to revisit existing items changing their color. -
@JonB I'm still wondering if the right approach would be to use a QItemViewDelegate subclass. I need custom painting of the rows when they come into view based on a global UI setting (colored or normal display) and the content of one of the cells in the row (the threadID).
The Star Delegate example seems to indicate that custom cell painting is done via these view delegates.
I thought that the way these delegates are supposed to work is that they get called for painting or editing on demand (i.e. when their cells are visible via the scrollbars or double clicked for editing).
This way the change from normal to colored rows would refresh the visible view (I don't really need to store the background with each item in the table as it is just for visibility purposes.