书接上文,我们继续来聊聊 QTableView 上功能的优化
多级表头,QTableView 原生不支持,需要我们继承 QHeaderView,自己实现
主要是通过 QPainter 实现 QHeaderView 的 paintSection 函数
x1void HeaderView::paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const2{3 painter->setPen(QColor(216,216,216));4
5 QStyleOptionHeader opt;6 initStyleOption(&opt);7 opt.textAlignment = Qt::AlignCenter;8 opt.section = logicalIndex;9
10 if(logicalIndex == 0 || logicalIndex == 1)11 {12 opt.text = QString("Group 1");13 opt.rect = QRect(0, 0, rect.width()*2, rect.height()/2);14 painter->fillRect(opt.rect, Qt::white);15 painter->drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());16 painter->drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());17 style()->drawControl(QStyle::CE_HeaderLabel, &opt, painter, this);18 }19
20 if(logicalIndex == 2 || logicalIndex == 3)21 {22 opt.text = QString("Group 2");23 opt.rect = QRect(rect.width()*2, 0, rect.width()*2, rect.height()/2);24 painter->fillRect(opt.rect, Qt::white);25 painter->drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());26 painter->drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());27 style()->drawControl(QStyle::CE_HeaderLabel, &opt, painter, this);28 }29
30 if(logicalIndex == 4 || logicalIndex == 5)31 {32 opt.text = QString("Group 3");33 opt.rect = QRect(rect.width()*4, 0, rect.width()*2, rect.height()/2);34 painter->fillRect(opt.rect, Qt::white);35 painter->drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());36 painter->drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());37 style()->drawControl(QStyle::CE_HeaderLabel, &opt, painter, this);38 }39
40 if(logicalIndex == 6)41 {42 opt.text = QString("Group 4");43 opt.rect = QRect(rect.width()*6, 0, rect.width(), rect.height()/2);44 painter->fillRect(opt.rect, Qt::white);45 painter->drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());46 painter->drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());47 style()->drawControl(QStyle::CE_HeaderLabel, &opt, painter, this);48 }49
50 opt.text = QString("Section %1").arg(logicalIndex);51 opt.rect = QRect(rect.width()*logicalIndex, rect.height()/2, rect.width(), rect.height()/2);52 painter->fillRect(opt.rect, Qt::white);53 painter->drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());54 painter->drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());55 style()->drawControl(QStyle::CE_HeaderLabel, &opt, painter, this);56}为了方便说明,精简了大量代码
但是重要的绘制代码,我没有用循环,而是重复了 5 遍
主要为了强调表头绘制并没有什么复杂的代码
paintSection 函数会在每个 section 绘制的时候调用
一个 section 就代表一列,图中有 7 个 section
我们来看 drawControl,它有 4 个参数,CE_HeaderLabel 代表绘制文字
opt 是文字的各项参数,这里主要设置了文字所占矩形框的大小
fillRect 是为每个矩形框绘制白色底色
drawLine 是为每个矩形框绘制底部和右侧的边框线
冻结列,官方提供了一个只有左冻结列的 example
主要思路是继承 QTableView 创建两个 TableViewBase,
然后将一个TableViewBase 叠在另一个 TableViewBase 上,但只显示左边几列,代表冻结列
最后绑定一些信号槽,使滚动条和鼠标事件有效
参考这个思路,我们可以写出右冻结列,上冻结行,下冻结行,固定位置的汇总行
xxxxxxxxxx471234
5TableView::TableView() {6 verticalHeader()->hide();7 setFrameShape(QFrame::NoFrame);8 setHorizontalScrollMode(ScrollPerPixel);9}10
11void TableView::setModel(QAbstractItemModel *model) {12 QTableView::setModel(model);13
14 leftFrozenTableView = new QTableView(this);15 leftFrozenTableView->setFocusPolicy(Qt::NoFocus);16 leftFrozenTableView->verticalHeader()->hide();17 leftFrozenTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);18 leftFrozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);19 leftFrozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);20 leftFrozenTableView->setModel(model);21 leftFrozenTableView->setFrameShape(QFrame::NoFrame);22
23 rightFrozenTableView = new QTableView(this);24 rightFrozenTableView->setFocusPolicy(Qt::NoFocus);25 rightFrozenTableView->verticalHeader()->hide();26 rightFrozenTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);27 rightFrozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);28 rightFrozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);29 rightFrozenTableView->setModel(model);30 rightFrozenTableView->setFrameShape(QFrame::NoFrame);31
32 viewport()->stackUnder(leftFrozenTableView);33
34 for (int col = 2; col < model->columnCount(); ++col) {35 leftFrozenTableView->setColumnHidden(col, true);36 }37
38 for (int col = 0; col < model->columnCount()-1; ++col) {39 rightFrozenTableView->setColumnHidden(col, true);40 }41
42 leftFrozenTableView->show();43 rightFrozenTableView->show();44
45 leftFrozenTableView->setGeometry(0, 0, columnWidth(0) + columnWidth(1), viewport()->height()+horizontalHeader()->height());46 rightFrozenTableView->setGeometry(viewport()->width()-columnWidth(6)-17, 0, columnWidth(6), viewport()->height()+horizontalHeader()->height());47}代码中对左侧两列做了冻结,对最后一列做了冻结,
最关键的有两处:
第32行,viewport()->stackUnder(leftFrozenTableView); 将原本的表格放置到最后;
最后两行 setGeometry,设置左冻结列和右冻结列的大小及位置
表头拖动
首先设置支持 drag setDragEnabled``(``true``)
然后设置支持 drop setAcceptDrops(true)
接着设置几个拖放事件的函数
xxxxxxxxxx91void HeaderView::dragEnterEvent(QDragEnterEvent *event) {2 event->accept();3}4void HeaderView::dragMoveEvent(QDragMoveEvent *event) {5 event->accept();6}7void HeaderView::dropEvent(QDropEvent *event) {8 event->accept();9}最后是最关键的 mousePress 事件的定义
xxxxxxxxxx551void HeaderView::mousePressEvent(QMouseEvent *event) {2 QDrag *drag = new QDrag(this);3 QMimeData *mimeData = new QMimeData;4 drag->setMimeData(mimeData);5
6 QPixmap pixmap(200, 80);7 pixmap.fill(Qt::transparent);8
9 QPainter painter;10 painter.begin(&pixmap);11
12 painter.setOpacity(0.7);13 painter.setPen(QColor(216,216,216));14
15 QStyleOptionHeader opt;16 initStyleOption(&opt);17 opt.textAlignment = Qt::AlignCenter;18 opt.section = 0;19 opt.text = QString("Group 1");20 opt.rect = QRect(0, 0, 200, 40);21 painter.fillRect(opt.rect, QColor(248, 248, 249));22 painter.setPen(QPen(QColor(232,234,236)));23 painter.drawLine(opt.rect.left(), opt.rect.top(), opt.rect.left(), opt.rect.bottom());24 painter.drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());25 painter.drawLine(opt.rect.left(), opt.rect.top(), opt.rect.right(), opt.rect.top());26 painter.drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());27 style()->drawControl(QStyle::CE_HeaderLabel, &opt, &painter, this);28
29 opt.text = QString("Section 0");30 opt.rect = QRect(0, 40, 100, 40);31 painter.fillRect(opt.rect, QColor(248, 248, 249));32 painter.setPen(QPen(QColor(232,234,236)));33 painter.drawLine(opt.rect.left(), opt.rect.top(), opt.rect.left(), opt.rect.bottom());34 painter.drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());35 painter.drawLine(opt.rect.left(), opt.rect.top(), opt.rect.right(), opt.rect.top());36 painter.drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());37 style()->drawControl(QStyle::CE_HeaderLabel, &opt, &painter, this);38
39 opt.text = QString("Section 1");40 opt.rect = QRect(100, 40, 100, 40);41 painter.fillRect(opt.rect, QColor(248, 248, 249));42 painter.setPen(QPen(QColor(232,234,236)));43 painter.drawLine(opt.rect.left(), opt.rect.top(), opt.rect.left(), opt.rect.bottom());44 painter.drawLine(opt.rect.right(), opt.rect.top(), opt.rect.right(), opt.rect.bottom());45 painter.drawLine(opt.rect.left(), opt.rect.top(), opt.rect.right(), opt.rect.top());46 painter.drawLine(opt.rect.left(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());47 style()->drawControl(QStyle::CE_HeaderLabel, &opt, &painter, this);48
49 painter.end();50
51 drag->setPixmap(pixmap);52 drag->setHotSpot(event->pos());53
54 drag->exec();55}我们创建了一个 QPixmap,然后用 paintSection 类似的代码画了文字和边框,
最后把这张图片交给 QDrag,就能在拖动的时候看到半透明的图片了