Why selectRow After insertRow Doesn't Work in PyQt6

Understanding how to properly insert rows in a QAbstractTableModel and select them in a QTableView
Heads up! You've already completed this tutorial.

I'm adding a row to a QTableView using a custom QAbstractTableModel, and then calling selectRow to 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:

python
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():

python
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.

Find out More

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:

python
self.model.rowsInserted.connect(self.on_rows_inserted)
python
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.

python
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.

Find out More

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

Why selectRow After insertRow Doesn't Work in PyQt6 was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.