Введение
Стандартное отображение ячеек в QTableView/QListView (через модель и стандартный делегат) подходит для простого текста, но когда нужна нестандартная визуализация — цветные индикаторы статуса, рейтинг звёздами, прогресс-бары прямо в ячейке таблицы — требуется собственный делегат. QStyledItemDelegate — базовый класс для этого: переопределяя paint() и опционально sizeHint(), можно полностью контролировать визуальное представление ячейки, не трогая саму модель данных. В статье построим делегат, отображающий рейтинг в виде звёзд.
Концепция
Делегат получает данные ячейки через QStyleOptionViewItem (содержит, в частности, область отрисовки rect и состояние — выделена ли ячейка, под курсором ли) и QModelIndex (для прямого обращения к данным модели через index.data(role)). Переопределение paint() рисует содержимое ячейки вручную через QPainter, полностью заменяя стандартную отрисовку текста. Для интерактивного редактирования (не только отображения) дополнительно переопределяются createEditor(), setEditorData(), setModelData().
Пример кода: Кастомная отрисовка таблиц
// starratingdelegate.h
#pragma once
#include <QStyledItemDelegate>
#include <QPainter>
#include <QApplication>
class StarRatingDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit StarRatingDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
const int rating = index.data(Qt::UserRole).toInt(); // от 0 до 5
painter->save();
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
const int starSize = 16;
const int spacing = 2;
int x = option.rect.left() + 4;
const int y = option.rect.top() + (option.rect.height() - starSize) / 2;
painter->setRenderHint(QPainter::Antialiasing);
for (int i = 0; i < 5; ++i) {
const QColor color = (i < rating) ? QColor(255, 193, 7) : QColor(220, 220, 220);
drawStar(painter, QRect(x, y, starSize, starSize), color);
x += starSize + spacing;
}
painter->restore();
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
Q_UNUSED(option);
Q_UNUSED(index);
return QSize(5 * 18 + 8, 24); // 5 звёзд + отступы
}
private:
void drawStar(QPainter *painter, const QRect &rect, const QColor &color) const
{
QPolygonF star;
const QPointF center = rect.center();
const double outerRadius = rect.width() / 2.0;
const double innerRadius = outerRadius * 0.4;
for (int i = 0; i < 10; ++i) {
const double angle = -M_PI / 2 + i * M_PI / 5;
const double radius = (i % 2 == 0) ? outerRadius : innerRadius;
star << QPointF(center.x() + radius * cos(angle), center.y() + radius * sin(angle));
}
painter->setBrush(color);
painter->setPen(Qt::NoPen);
painter->drawPolygon(star);
}
};
// Использование с QTableView
#include <QTableView>
#include <QStandardItemModel>
#include "starratingdelegate.h"
void setupRatingTable(QTableView *tableView)
{
auto *model = new QStandardItemModel(3, 1, tableView);
auto setRating = [model](int row, int rating) {
model->setData(model->index(row, 0), rating, Qt::UserRole);
};
setRating(0, 5);
setRating(1, 3);
setRating(2, 1);
tableView->setModel(model);
tableView->setItemDelegateForColumn(0, new StarRatingDelegate(tableView));
}
Пояснения к коду
paint() полностью игнорирует стандартный механизм отрисовки текста и вместо этого читает рейтинг из роли Qt::UserRole через index.data(), после чего рисует пять звёзд, закрашенных или серых в зависимости от того, входит ли индекс звезды в значение рейтинга. Метод drawStar() строит геометрию пятиконечной звезды через тригонометрические вычисления координат вершин и закрашивает получившийся QPolygonF.
Обработка состояния выделения (option.state & QStyle::State_Selected) — важная деталь: без неё выделенная ячейка таблицы выглядела бы так же, как невыделенная, что нарушает стандартные пользовательские ожидания от поведения таблицы. setItemDelegateForColumn() устанавливает кастомный делегат только для конкретного столбца, оставляя остальные столбцы с поведением по умолчанию.
Подводные камни
- Отсутствие
painter->save()/painter->restore()вокруг кастомной отрисовки может «протечь» изменённые настройкиQPainter(кисть, перо, шрифт) в отрисовку следующих ячеек или элементов — особенно важно при наличии множества разных делегатов или смешанного использования стандартной и кастомной отрисовки в одной таблице. - Неверный
sizeHint()или его полное отсутствие приводит к тому, что таблица выделяет под ячейку размер, рассчитанный по стандартной логике (исходя из текста), который может не соответствовать реальному визуальному содержимому делегата — звёзды могут обрезаться или, наоборот, оставлять много пустого пространства. - Тяжёлые вычисления (как генерация полигона звезды) внутри
paint()при каждой перерисовке, без кэширования результата — для таблиц с большим числом строк и частыми перерисовками (например, при скролле) стоит кэшировать заранее вычисленную геометрию звезды как статическую константу, а не пересчитывать её координаты на каждый вызовpaint(). - Игнорирование доступности (accessibility) при полностью кастомной визуализации. Стандартный текстовый делегат автоматически интегрируется с screen reader’ами через текстовое представление данных — полностью кастомная графическая отрисовка (звёзды без текстовой альтернативы) может сделать таблицу непригодной для пользователей с нарушениями зрения без дополнительной работы по обеспечению доступности.