在进入源码刨析之前,首先简单整理了下TableView的类图,并列出了在下认为比较重要的函数和属性。
1) QAbstractItemView继承自QAbstractScrollArea, 在QAbstractScrollArea中有水平与垂直的2个QScrollBar控件, 并根据QScrollBar的信号,调用scrollContentsBy( dx:int, dy:int)这个函数,重写这个函数来计算表格水平与垂直滚动的距离。
2) QAbstractItemView中通过setModel函数设置一个QAbstractItemModel的指针,QAbstractItemModel通过接口向整个架构提供数据支持,并通过role的区分来返回各种关于样式、关于用户自定义数据等信息,非常灵活。
3) QTableView继承自QAbstractItemView, 默认生成2个同样继承自QAbstractItemView的QHreaderView, 作为表头的显示,并给QTableView提供表的行列数与宽高信息(QHeaderView的行列信息同样是从model的接口获取的, Qt在这里做了进一步封装,QHeaderView同样还处理隐藏行/列的逻辑处理等)。
4)QAbstractItemView 具有一个默认的继承自QAbstractItemDelegate对象来实现绘制,
外部可以通过设置自定义的Delegate来替代默认绘制。
5)QModelIndex 在图中没有直接表示出来,这个类里主要包含了当前View的model指针,行列号, 这样就可以根据行号、列号、role向model获取/设置数据。(QModelIndex还实现了树的管理,这在QTreeView的时候有体现, 这里只讨论QTableview,有机会研究QTreeView再细说!)
6)QAbstractItemDelegate并没有直接依赖Model, 但是在它的每个函数中都会有一个QModelIndex对象来获取model中的信息。delegate将会根据从模型获取的信息,与sytle的属性进行一个组装(option:QStyleOptionViewItem),然后再交给QStyle进行绘制(这里会根据各个平台的风格,用QPainter进行最终的绘制)。
上面简述了各个类的作用,接下来看看QTableview在PaintEvent中是怎么去绘制表格的:
void QTableView::paintEvent(QPaintEvent *event)
{
Q_D(QTableView);
// setup temp variables for the painting
QStyleOptionViewItem option = d->viewOptionsV1(); //**保存了绘制Item的所有信息
//...... 初始化一些属性
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;
const int x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1);
const int y = verticalHeader->length() - verticalHeader->offset() - 1;
//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)); //这里根据视口大小计算了一共可以绘制的Item数量
const QRegion region = event->region().translated(offset); //需要重新绘制的region
if (d->hasSpans()) {
d->drawAndClipSpans(region, &painter, option, &drawn,
firstVisualRow, lastVisualRow, firstVisualColumn, l astVisualColumn); //如果有Span,绘制span, 这里暂不讨论
}
//开始遍历dirty的rect,然后根据rect计算出Item的行列进行绘制
for (QRect dirtyArea : region)
{
//......
// get the horizontal start and end visual sections
int left = horizontalHeader->visualIndexAt(dirtyArea.left());
int right = horizontalHeader->visualIndexAt(dirtyArea.right());
//......
// get the vertical start and end visual sections and if alternate color
int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom());
//top
top = verticalHeader->visualIndexAt(dirtyArea.top());
// Paint each row item
for (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex)
{
int row = verticalHeader->logicalIndex(visualRowIndex);
// Paint each column item
for (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex)
{
int col = horizontalHeader->logicalIndex(visualColumnIndex);
//......
const QModelIndex index = d->model->index(row, col, d->root);
if (index.isValid()) {
option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh); //绘制的rect信息赋值给option的rect
if (alternate) {
if (alternateBase)
option.features |= QStyleOptionViewItem::Alternate;
else
option.features &= ~QStyleOptionViewItem::Alternate;
}
d->drawCell(&painter, option, index); //调用d指针的drawCell函数进行绘制
}
}
}
}
}
void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
{
Q_Q(QTableView);
QStyleOptionViewItem 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;
}
//以上就是根据一些状态装配option,比如是否选中、Enable、focus等
q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q);//先调用QStyle的实例绘制表格单元的背景
q->itemDelegate(index)->paint(painter, opt, index); //调用delegate的paint函数绘制item
}
总结下QTableView在PaintEvent中所作的主要操作:
1) 遍历dirtyArea,根据dirtyArea计算出需要绘制的的行列范围。
2) 循环绘制每一行的每一个Item, 主要根据row、col得到QModelindex。
3) 将painter、option、index交给private的drawCell函数进行绘制。
4)drawCell函数中,首先根据一些单元格状态对option进行配置,再交给QStyle()绘制背景,最终调用delegate执行item的绘制。 如果我们为该item自定义了Delegate,比如在上一节中,我们是在自定义的Delegate的paint函数中根据分数(从index中获取)绘制了不同朵数的小花花,就在这里代替了默认的Delegate绘制。
在下一节中,再去看QStyle是怎么去绘制不同风格的图元,以及样式表(QSS)是如何生效的!