I'm adding a row to a QTableView using a custom QAbstractTableModel, and then calling
selectRowto highlight the new row. But the selection doesn't work — the row never gets selected. What am I doing wrong?
This is a common stumbling block when working with Qt's model/view architecture. The root cause usually comes down to two things: not using beginInsertRows()/endInsertRows() to notify the view properly, and passing the wrong row index to selectRow(). Let's walk through how to get this right.
How Qt Expects You to Insert Rows
When you subclass QAbstractTableModel, Qt provides a specific protocol for adding and removing rows. You need to call beginInsertRows() before modifying your data, and endInsertRows() after. These methods tell the view that the model's structure is about to change, and then that it has finished changing. The view uses this information to update itself, including keeping the selection model in sync.
A common mistake is to emit the rowsAboutToBeInserted and rowsInserted signals directly. These signals are meant to be emitted by beginInsertRows() and endInsertRows() internally — you should never emit them yourself. When you do, the view doesn't get the full notification it needs, and things like selection and scrolling can silently fail.
Here's what a correct insertRow implementation looks like:
def insertRow(self, row, parent=QModelIndex()):
self.beginInsertRows(parent, row, row)
self._data.insert(row, [0, 0, 0]) # Insert default data at position `row`
self.endInsertRows()
return True
Notice the method signature: Qt's insertRow expects a row parameter (the position to insert at) and an optional parent index. It returns a bool indicating success. Keeping your method signature consistent with the base class helps avoid confusion and makes your model work correctly with other Qt components.
The same pattern applies to removing rows — use beginRemoveRows() and endRemoveRows():
def removeRow(self, row, parent=QModelIndex()):
self.beginRemoveRows(parent, row, row)
del self._data[row]
self.endRemoveRows()
return True
Getting the Row Index Right
The second part of the problem is about indexing. If your model currently has 3 rows (indices 0, 1, 2), and you append a new row, the new row will be at index 3. But if you accidentally calculate the index as len(self._data) + 1 before appending, you'll get 4 — which is out of bounds. Calling selectRow(4) on a table with 4 rows (indices 0–3) quietly does nothing.
Qt doesn't raise an error when you call selectRow() with an out-of-bounds index. It simply ignores the call. This can be confusing when you're debugging, because there's no traceback or warning — the row just doesn't get selected.
PyQt/PySide Development Services — Stuck in development hell? I'll help you get your project focused, finished and released. Benefit from years of practical experience releasing software with Python.
When inserting at the end of the table, the correct row index is len(self._data) (before the insert) or len(self._data) - 1 (after the insert). Either way, make sure the number you pass to selectRow() matches the actual position of the new row in the model.
Connecting Model Signals to the Selection Model
Rather than manually calling selectRow() right after inserting, a cleaner approach is to connect the model's rowsInserted signal to a slot that updates the selection. This way, any time a row is inserted — regardless of where or how — it automatically gets selected. If you're new to how signals and slots work in PyQt6, this pattern is a great example of their power.
The rowsInserted signal provides the parent index, the first row inserted, and the last row inserted. You can use the last parameter to select the newly added row:
self.model.rowsInserted.connect(self.on_rows_inserted)
def on_rows_inserted(self, parent, first, last):
self.table.selectRow(last)
This decouples the insert logic from the selection logic, which makes your code easier to maintain.
Complete Working Example
Here's a full example in PyQt6 that demonstrates correct row insertion, removal, and automatic selection of newly added rows. If you want to learn more about using QTableView with custom data sources including NumPy and Pandas, see the QTableView with ModelViews tutorial.
import sys
from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt6.QtWidgets import (
QApplication,
QHBoxLayout,
QMainWindow,
QPushButton,
QTableView,
QVBoxLayout,
QWidget,
)
class TableModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index, role):
if role == Qt.ItemDataRole.DisplayRole:
value = self._data[index.row()][index.column()]
return str(value)
def setData(self, index, value, role):
if role == Qt.ItemDataRole.EditRole:
self._data[index.row()][index.column()] = value
return True
return False
def rowCount(self, parent=QModelIndex()):
return len(self._data)
def columnCount(self, parent=QModelIndex()):
if self._data:
return len(self._data[0])
return 0
def flags(self, index):
return (
Qt.ItemFlag.ItemIsSelectable
| Qt.ItemFlag.ItemIsEnabled
| Qt.ItemFlag.ItemIsEditable
)
def insertRow(self, row, parent=QModelIndex()):
"""Insert a blank row at the given position."""
self.beginInsertRows(parent, row, row)
self._data.insert(row, [0, 0, 0])
self.endInsertRows()
return True
def setRowData(self, row, values):
"""Set the data for all columns in a given row."""
for col, value in enumerate(values):
index = self.index(row, col)
self.setData(index, value, Qt.ItemDataRole.EditRole)
def removeRow(self, row, parent=QModelIndex()):
if 0 <= row < len(self._data):
self.beginRemoveRows(parent, row, row)
del self._data[row]
self.endRemoveRows()
return True
return False
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(350, 200)
self.setWindowTitle("Insert and Select Rows")
self.row_data_counter = 10
# Set up the table view.
self.table = QTableView()
self.table.setSelectionMode(
QTableView.SelectionMode.SingleSelection
)
self.table.setSelectionBehavior(
QTableView.SelectionBehavior.SelectRows
)
# Set up the model with one initial row.
data = [[1, 9, 2]]
self.model = TableModel(data)
self.table.setModel(self.model)
# When a row is inserted, automatically select it.
self.model.rowsInserted.connect(self.on_rows_inserted)
# Buttons for adding and deleting rows.
self.add_button = QPushButton("Add")
self.delete_button = QPushButton("Delete")
self.add_button.clicked.connect(self.add_row)
self.delete_button.clicked.connect(self.delete_row)
# Layout.
button_layout = QHBoxLayout()
button_layout.addWidget(self.add_button)
button_layout.addWidget(self.delete_button)
main_layout = QVBoxLayout()
main_layout.addWidget(self.table)
main_layout.addLayout(button_layout)
container = QWidget()
container.setLayout(main_layout)
self.setCentralWidget(container)
def on_rows_inserted(self, parent, first, last):
"""Select the most recently inserted row."""
self.table.selectRow(last)
def add_row(self):
col = self.row_data_counter
new_data = [col, col + 1, col + 2]
self.row_data_counter += 10
# Insert at the end of the table.
row = self.model.rowCount()
self.model.insertRow(row)
self.model.setRowData(row, new_data)
def delete_row(self):
indexes = self.table.selectionModel().selectedRows()
if indexes:
# Remove the first selected row.
row = indexes[0].row()
self.model.removeRow(row)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Try running this and clicking Add a few times. Each new row appears at the bottom of the table and is immediately selected. Click Delete to remove the currently selected row. To make the table cells editable, see our guide on editing a PyQt6 QTableView.
Bring Your PyQt/PySide Application to Market — Specialized launch support for scientific and engineering software built using Python & Qt.