Qt自定义表格实现与源码阅读二

         在进入源码刨析之前,首先简单整理了下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)是如何生效的!

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值