QTableView paint()

Может показаться на каком-то этапе развития, что вы все знаете о QTableView, но он может преподнести сюрпризы с отрисовкой. Поэтому неплохо бы раскрутить отрисовку на уровне исходников.

void QTableView::paintEvent(QPaintEvent *event)
{
    Q_D(QTableView);
    // setup temp variables for the painting
    QStyleOptionViewItemV4 option = d->viewOptionsV4();
    const QPoint offset = d->scrollDelayOffset;
    const bool showGrid = d->showGrid;
    const int gridSize = showGrid ? 1 : 0;
    const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
    const QColor gridColor = static_cast(gridHint);
    const QPen gridPen = QPen(gridColor, 0, d->gridStyle);
    const QHeaderView *verticalHeader = d->verticalHeader;
    const QHeaderView *horizontalHeader = d->horizontalHeader;
    const bool alternate = d->alternatingColors;
    const bool rightToLeft = isRightToLeft();

    QPainter painter(d->viewport);

    // if there's nothing to do, clear the area and return
    if (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate)
        return;

    uint x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1);
    uint y = verticalHeader->length() - verticalHeader->offset() - 1;

    const QRegion region = event->region().translated(offset);
    const QVector rects = region.rects();

    //firstVisualRow is the visual index of the first visible row.  lastVisualRow is the visual index of the last visible Row.
    //same goes for ...VisualColumn
    int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0);
    int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->viewport()->height());
    if (lastVisualRow == -1)
        lastVisualRow = d->model->rowCount(d->root) - 1;

    int firstVisualColumn = horizontalHeader->visualIndexAt(0);
    int lastVisualColumn = horizontalHeader->visualIndexAt(horizontalHeader->viewport()->width());
    if (rightToLeft)
        qSwap(firstVisualColumn, lastVisualColumn);
    if (firstVisualColumn == -1)
        firstVisualColumn = 0;
    if (lastVisualColumn == -1)
        lastVisualColumn = horizontalHeader->count() - 1;

    QBitArray drawn((lastVisualRow - firstVisualRow   1) * (lastVisualColumn - firstVisualColumn   1));

    if (d->hasSpans()) {
        d->drawAndClipSpans(region, &painter, option, &drawn,
                             firstVisualRow, lastVisualRow, firstVisualColumn, lastVisualColumn);
    }

    for (int i = 0; i < rects.size();   i) {
        QRect dirtyArea = rects.at(i);
        dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y)));
        if (rightToLeft) {
            dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x)));
        } else {
            dirtyArea.setRight(qMin(dirtyArea.right(), int(x)));
        }

        // get the horizontal start and end visual sections
        int left = horizontalHeader->visualIndexAt(dirtyArea.left());
        int right = horizontalHeader->visualIndexAt(dirtyArea.right());
        if (rightToLeft)
            qSwap(left, right);
        if (left == -1) left = 0;
        if (right == -1) right = horizontalHeader->count() - 1;

        // get the vertical start and end visual sections and if alternate color
        int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom());
        if (bottom == -1) bottom = verticalHeader->count() - 1;
        int top = 0;
        bool alternateBase = false;
        if (alternate && verticalHeader->sectionsHidden()) {
            uint verticalOffset = verticalHeader->offset();
            int row = verticalHeader->logicalIndex(top);
            for (int y = 0;
                 ((uint)(y  = verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom);
                   top) {
                row = verticalHeader->logicalIndex(top);
                if (alternate && !verticalHeader->isSectionHidden(row))
                    alternateBase = !alternateBase;
            }
        } else {
            top = verticalHeader->visualIndexAt(dirtyArea.top());
            alternateBase = (top & 1) && alternate;
        }
        if (top == -1 || top > bottom)
            continue;

        // Paint each row item
        for (int visualRowIndex = top; visualRowIndex <= bottom;   visualRowIndex) {
            int row = verticalHeader->logicalIndex(visualRowIndex);
            if (verticalHeader->isSectionHidden(row))
                continue;
            int rowY = rowViewportPosition(row);
            rowY  = offset.y();
            int rowh = rowHeight(row) - gridSize;

            // Paint each column item
            for (int visualColumnIndex = left; visualColumnIndex <= right;   visualColumnIndex) {
                int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn   1)
                          visualColumnIndex - firstVisualColumn;

                if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit))
                    continue;
                drawn.setBit(currentBit);

                int col = horizontalHeader->logicalIndex(visualColumnIndex);
                if (horizontalHeader->isSectionHidden(col))
                    continue;
                int colp = columnViewportPosition(col);
                colp  = offset.x();
                int colw = columnWidth(col) - gridSize;

                const QModelIndex index = d->model->index(row, col, d->root);
                if (index.isValid()) {
                    option.rect = QRect(colp   (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh);
                    if (alternate) {
                        if (alternateBase)
                            option.features |= QStyleOptionViewItemV2::Alternate;
                        else
                            option.features &= ~QStyleOptionViewItemV2::Alternate;
                    }
                    d->drawCell(&painter, option, index);
                }
            }
            alternateBase = !alternateBase && alternate;
        }

        if (showGrid) {
            // Find the bottom right (the last rows/columns might be hidden)
            while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom;
            QPen old = painter.pen();
            painter.setPen(gridPen);
            // Paint each row
            for (int visualIndex = top; visualIndex <= bottom;   visualIndex) {
                int row = verticalHeader->logicalIndex(visualIndex);
                if (verticalHeader->isSectionHidden(row))
                    continue;
                int rowY = rowViewportPosition(row);
                rowY  = offset.y();
                int rowh = rowHeight(row) - gridSize;
                painter.drawLine(dirtyArea.left(), rowY   rowh, dirtyArea.right(), rowY   rowh);
            }

            // Paint each column
            for (int h = left; h <= right;   h) {
                int col = horizontalHeader->logicalIndex(h);
                if (horizontalHeader->isSectionHidden(col))
                    continue;
                int colp = columnViewportPosition(col);
                colp  = offset.x();
                if (!rightToLeft)
                    colp  =  columnWidth(col) - gridSize;
                painter.drawLine(colp, dirtyArea.top(), colp, dirtyArea.bottom());
            }

            //draw the top & left grid lines if the headers are not visible.
            //We do update this line when subsequent scroll happen (see scrollContentsBy)
            if (horizontalHeader->isHidden() && verticalScrollMode() == ScrollPerItem)
                painter.drawLine(dirtyArea.left(), 0, dirtyArea.right(), 0);
            if (verticalHeader->isHidden() && horizontalScrollMode() == ScrollPerItem)
                painter.drawLine(0, dirtyArea.top(), 0, dirtyArea.bottom());
            painter.setPen(old);
        }
    }

#ifndef QT_NO_DRAGANDDROP
    // Paint the dropIndicator
    d->paintDropIndicator(&painter);
#endif
}







void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItemV4 &option, const QModelIndex &index)
{
    Q_Q(QTableView);
    QStyleOptionViewItemV4 opt = option;

    if (selectionModel && selectionModel->isSelected(index))
        opt.state |= QStyle::State_Selected;
    if (index == hover)
        opt.state |= QStyle::State_MouseOver;
    if (option.state & QStyle::State_Enabled) {
        QPalette::ColorGroup cg;
        if ((model->flags(index) & Qt::ItemIsEnabled) == 0) {
            opt.state &= ~QStyle::State_Enabled;
            cg = QPalette::Disabled;
        } else {
            cg = QPalette::Normal;
        }
        opt.palette.setCurrentColorGroup(cg);
    }

    if (index == q->currentIndex()) {
        const bool focus = (q->hasFocus() || viewport->hasFocus()) && q->currentIndex().isValid();
        if (focus)
            opt.state |= QStyle::State_HasFocus;
    }

    q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q);

    q->itemDelegate(index)->paint(painter, opt, index);
}



void QStyledItemDelegate::paint(QPainter *painter,
        const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_ASSERT(index.isValid());

    QStyleOptionViewItemV4 opt = option;
    initStyleOption(&opt, index);

    const QWidget *widget = QStyledItemDelegatePrivate::widget(option);
    QStyle *style = widget ? widget->style() : QApplication::style();
    style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}




void QStyledItemDelegate::initStyleOption(QStyleOptionViewItem *option,
                                         const QModelIndex &index) const
{
    QVariant value = index.data(Qt::FontRole);
    if (value.isValid() && !value.isNull()) {
        option->font = qvariant_cast(value).resolve(option->font);
        option->fontMetrics = QFontMetrics(option->font);
    }

    value = index.data(Qt::TextAlignmentRole);
    if (value.isValid() && !value.isNull())
        option->displayAlignment = Qt::Alignment(value.toInt());

    value = index.data(Qt::ForegroundRole);
    if (value.canConvert())
        option->palette.setBrush(QPalette::Text, qvariant_cast(value));

    if (QStyleOptionViewItemV4 *v4 = qstyleoption_cast(option)) {
        v4->index = index;
        QVariant value = index.data(Qt::CheckStateRole);
        if (value.isValid() && !value.isNull()) {
            v4->features |= QStyleOptionViewItemV2::HasCheckIndicator;
            v4->checkState = static_cast(value.toInt());
        }

        value = index.data(Qt::DecorationRole);
        if (value.isValid() && !value.isNull()) {
            v4->features |= QStyleOptionViewItemV2::HasDecoration;
            switch (value.type()) {
            case QVariant::Icon: {
                v4->icon = qvariant_cast(value);
                QIcon::Mode mode;
                if (!(option->state & QStyle::State_Enabled))
                    mode = QIcon::Disabled;
                else if (option->state & QStyle::State_Selected)
                    mode = QIcon::Selected;
                else
                    mode = QIcon::Normal;
                QIcon::State state = option->state & QStyle::State_Open ? QIcon::On : QIcon::Off;
                v4->decorationSize = v4->icon.actualSize(option->decorationSize, mode, state);
                break;
            }
            case QVariant::Color: {
                QPixmap pixmap(option->decorationSize);
                pixmap.fill(qvariant_cast(value));
                v4->icon = QIcon(pixmap);
                break;
            }
            case QVariant::Image: {
                QImage image = qvariant_cast(value);
                v4->icon = QIcon(QPixmap::fromImage(image));
                v4->decorationSize = image.size();
                break;
            }
            case QVariant::Pixmap: {
                QPixmap pixmap = qvariant_cast(value);
                v4->icon = QIcon(pixmap);
                v4->decorationSize = pixmap.size();
                break;
            }
            default:
                break;
            }
        }

        value = index.data(Qt::DisplayRole);
        if (value.isValid() && !value.isNull()) {
            v4->features |= QStyleOptionViewItemV2::HasDisplay;
            v4->text = displayText(value, v4->locale);
        }

        v4->backgroundBrush = qvariant_cast(index.data(Qt::BackgroundRole));
    }
}


void QStyledItemDelegate::paint(QPainter *painter,
        const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_ASSERT(index.isValid());

    QStyleOptionViewItemV4 opt = option;
    initStyleOption(&opt, index);

    const QWidget *widget = QStyledItemDelegatePrivate::widget(option);
    QStyle *style = widget ? widget->style() : QApplication::style();
    style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}

Надо сказать, что код не простой, но разобраться (тем более с помощью отладчика) можно. И оказывается тут происходит (как ни странно) отрисовка всех ячеек таблицы последовательно построчно (сверху вниз) и внутри строки по столбцам (слева направо). Все банально. 

Тут идёт обход всех ячеек таблицы и отрисовка их.

Небольшой нюанс о том, что :

initStyleOption(&opt, index);

устанавливает в том числе в opt поле text значение, запрашивая из модели данных значение ячейки по индексу.

Мы видим в коде, что для всех ячеек используется так называемый делегат (по умолчанию) QStyledItemDelegate. Он отрисовывает через свой paint() каждую ячейку.

Все это очень поможет при написании собственных делегатов, при их грамотной отрисовке . Смотрите здесь страницы: QComboBox делегатДелегаты

Эта страница вообще говоря показывает как на самом деле все просто, если у тебя есть исходный код и ты понимаешь его.

Интересная идея про то, как написать свой QTableView на странице QTableView реализуем свой функционал.