Qt的一些经验总结(7)-- QTableview单元格的选中刷新机制

338 阅读1分钟

问题

前提条件

点击一行单元格,只会高亮后面的,前面几个合并单元格是不会高亮。现在能高亮,是通过自定义delegate,在paint的时候判断后面的单元格选中状态来实现的,实际也导致了,前面几个合并的单元格虽然高亮,但实际并没有被选中。

2024-02-01 09-53-30 的屏幕截图.png

2024-02-01 09-53-49 的屏幕截图.png

源码调研

void QAbstractItemView::mousePressEvent(QMouseEvent *event)
{
// 省略代码
// 鼠标点击后,会调用setSelection函数
// 在qtableview中有对setSelection进行复写,主要的原因是针对单元格区域处理
    if ((command & QItemSelectionModel::Current) == 0) {
        setSelection(QRect(pos, QSize(1, 1)), command);
    } else {
        QRect rect(visualRect(d->currentSelectionStartIndex).center(), pos);
        setSelection(rect, command);
    }
// 省略代码
}

void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
    // 省略部分代码
    // selectionModel内部有对应的connect监听
    d->selectionModel->select(selection, command);
}

// 绑定到该函数上
// 因为前面的合并单元格是处于假选状态,所以对应的deselected是不包含这些合并的单元格
// 所以在viewpoert的update区域,只计算了那些真正选中的单元格的区域
// 目前的方案是复写visualRegionForSelection这个函数,来增加选中的区域
void QAbstractItemView::selectionChanged(const QItemSelection &selected,
                                         const QItemSelection &deselected)
{
    Q_D(QAbstractItemView);
    if (isVisible() && updatesEnabled()) {
        d->viewport->update(visualRegionForSelection(deselected) | visualRegionForSelection(selected));
    }
}

方案

auto viewportRect = this->viewport()->rect();
auto region = QTableView::visualRegionForSelection(selection);
auto indexes = selection.indexes();
// 计算可视范围内的单元格
int visualFirstRow = this->verticalHeader()->logicalIndexAt({0, 0});
int visualLastRow = this->verticalHeader()->logicalIndexAt({0, this->height()});
if (visualLastRow == -1) {
    visualLastRow = this->model()->rowCount();
}
int visualFirstCol = this->horizontalHeader()->logicalIndexAt({0, 0});
int visualLastCol = this->horizontalHeader()->logicalIndexAt({this->width(), 0});
if (visualLastCol == -1) {
    visualLastCol = this->model()->columnCount();
}
// 将可视范围内关联的选中单元格,计算出区域加入到刷新的区域中
for (auto &index : indexes) {
    int row = index.row();
    int col = index.column();
    if (col < visualFirstCol || row < visualFirstRow) {
        continue;
    }
    if (col > visualLastCol || row > visualLastRow) {
        break;
    }
    auto visualRect = this->visualRect(index);
    auto intersectedRect = viewportRect.intersected(visualRect);
    if (!intersectedRect.isEmpty()) {
        region |= intersectedRect;
    }
}
return region;