问题
前提条件
点击一行单元格,只会高亮后面的,前面几个合并单元格是不会高亮。现在能高亮,是通过自定义delegate,在paint的时候判断后面的单元格选中状态来实现的,实际也导致了,前面几个合并的单元格虽然高亮,但实际并没有被选中。
源码调研
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;