Implementation Overview
This article demonstrates how to implement custom selection area rendering for Qt's QTableView component. While Qt provides built-in selection functionality, it lacks customization options for border colors and visual details. The solution involves custom painting techniques to achieve Excel-like selection visuals.
Visual Results
The implementation produces a clean selection rectangle with customizable borders that highlight selected cell ranges effectively.
Technical Approach
The solution addresses three key aspects: determining the selection area, calculating border requirements, and implementing the rendering mechanism.
Selection Area Detection
Qt's selection model provides the necessary signals to track selection changes. The QItemSelectionModel::selectionChanged signal is connected to handle selection updates:
connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &CustomTableWidget::handleSelectionChange);
Border Calculation Logic
For each selected cell, the algorithm determines which borders should be drawn by checking adjacent cells:
void CustomTableWidget::handleSelectionChange(const QItemSelection &selected,
const QItemSelection &deselected)
{
QModelIndexList selectedIndices = tableView->selectionModel()->selectedIndexes();
int rowCount = model()->rowCount();
int columnCount = model()->columnCount();
// Create selection grid
QVector<qvector>> selectionGrid(rowCount, QVector<bool>(columnCount, false));
foreach (const QModelIndex &index, selectedIndices) {
selectionGrid[index.row()][index.column()] = true;
}
QMap<qmodelindex borderflags=""> borderData;
foreach (const QModelIndex &index, selectedIndices) {
BorderFlags flags = NoBorder;
// Check top border
if (index.row() == 0 || !selectionGrid[index.row()-1][index.column()]) {
flags |= TopBorder;
}
// Check right border
if (index.column() == columnCount-1 || !selectionGrid[index.row()][index.column()+1]) {
flags |= RightBorder;
}
// Check bottom border
if (index.row() == rowCount-1 || !selectionGrid[index.row()+1][index.column()]) {
flags |= BottomBorder;
}
// Check left border
if (index.column() == 0 || !selectionGrid[index.row()][index.column()-1]) {
flags |= LeftBorder;
}
borderData[index] = flags;
}
updateTableView(borderData);
}</qmodelindex></bool></qvector>
Rendering Implementation
The rendering is split between a custom delegate for selection highlighting and viewport painting for borders.
Selection Highlighting
A custom QStyledItemDelegate handles selection area filling:
void SelectionDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
if (selectedCells.contains(index)) {
painter->save();
painter->fillRect(option.rect, QColor(100, 0, 0, 100));
painter->restore();
}
}
Border Drawing
Custom border rendering occurs in the table view's paintEvent after calling the base implementation:
void CustomTableView::paintEvent(QPaintEvent *event)
{
QTableView::paintEvent(event);
QPainter painter(viewport());
painter.setRenderHint(QPainter::Antialiasing);
QPen borderPen(Qt::blue);
borderPen.setWidth(2);
painter.setPen(borderPen);
foreach (const QModelIndex &index, borderCells.keys()) {
BorderFlags flags = borderCells.value(index);
QRect cellRect = visualRect(index);
if (flags & TopBorder) {
painter.drawLine(cellRect.topLeft(), cellRect.topRight());
}
if (flags & RightBorder) {
painter.drawLine(cellRect.topRight(), cellRect.bottomRight());
}
if (flags & BottomBorder) {
painter.drawLine(cellRect.bottomLeft(), cellRect.bottomRight());
}
if (flags & LeftBorder) {
painter.drawLine(cellRect.topLeft(), cellRect.bottomLeft());
}
}
}
This approach ensures that selection borders are drawn as clean lines around the selected area while maintaining Qt's standard rendering for other table elements.