How to Clear and Refresh ComboBox Delegate Data in QTableView

Keep your QTableView ComboBox delegates in sync when your underlying data changes
Heads up! You've already completed this tutorial.

If you've set up a QTableView with a custom QComboBox delegate, you might run into a frustrating problem: when the underlying data in your model changes, the ComboBox items don't update. The delegate keeps showing the old values, even though your model clearly has the new data.

In this article, we'll walk through why this happens and how to fix it properly using model reset signals.

The Problem: Stale ComboBox Data

Imagine you have a QTableView backed by a QAbstractTableModel, with a QItemDelegate subclass that creates a QComboBox editor for a specific column. Something like this:

python
class ComboDelegate(QItemDelegate):
    """
    A delegate that places a QComboBox in every cell of the given column.
    """

    def __init__(self, parent):
        super().__init__(parent)

    def createEditor(self, parent, option, index):
        combobox = QComboBox(parent)
        version_list = []
        for item in index.data():
            if item not in version_list:
                version_list.append(item)
        combobox.addItems(version_list)
        return combobox

    def setEditorData(self, editor, index):
        value = index.data()
        if value:
            max_val = len(value)
            editor.setCurrentIndex(max_val - 1)

    def setModelData(self, editor, model, index):
        pass

You assign this delegate to a column on your table view:

python
delegate = ComboDelegate(self)
self.ui.table_view.setItemDelegateForColumn(col_id, delegate)

Everything looks great on the first load. But then your data changes — maybe you fetch fresh results from a database or an API — and the ComboBoxes still show the old items.

You might try recreating the delegate, or removing and re-adding it. None of that helps, because the issue isn't with the delegate itself — it's with the view not knowing that it needs to re-query the model.

Why This Happens

When a QTableView displays data, it caches information about what it's showing. The delegate's createEditor method is called when a cell enters editing mode, and it reads the data from the model at that point. But the view won't automatically ask the delegate to recreate editors or refresh displayed data unless it knows the model has changed.

The view listens for specific signals from the model to know when data has been added, removed, or changed. If your model's data changes but the model doesn't emit the right signals, the view has no reason to refresh anything.

Resetting the Model

The simplest way to tell the view that the entire dataset has changed is to perform a model reset. You do this by calling beginResetModel() and endResetModel() on your model, wrapping the point where you update the data:

python
self.table_model.beginResetModel()
# Update your internal data here
self.table_model._data = new_data  # or however you store your data
self.table_model.endResetModel()

The beginResetModel() call tells any connected views that the model is about to change completely. The endResetModel() call tells them the change is done and they should refresh everything — layout, data, delegates, all of it.

This pair of calls causes the view to discard any cached state and re-request everything from the model. When the delegate's createEditor is next called, it will pull the fresh data from the model via index.data(), and your ComboBoxes will show the updated items.

A Complete Working Example

Here's a full example that demonstrates this in action. The table has a column where each cell contains a list of version strings, displayed via a ComboBox delegate. A button lets you swap in a completely new dataset — and because we use beginResetModel()/endResetModel(), the ComboBoxes update correctly.

python
import sys

from PyQt6.QtCore import QAbstractTableModel, Qt
from PyQt6.QtWidgets import (
    QApplication,
    QComboBox,
    QHBoxLayout,
    QItemDelegate,
    QPushButton,
    QTableView,
    QVBoxLayout,
    QWidget,
)


INITIAL_DATA = [
    ["Alice", ["1.0", "1.1", "1.2"]],
    ["Bob", ["2.0", "2.1"]],
    ["Charlie", ["3.0", "3.1", "3.2", "3.3"]],
]

UPDATED_DATA = [
    ["Alice", ["4.0", "4.1"]],
    ["Bob", ["5.0", "5.1", "5.2", "5.3"]],
    ["Charlie", ["6.0"]],
]


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        self._headers = ["Name", "Versions"]

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._headers)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]
        return None

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return self._headers[section]
        return None

    def update_data(self, new_data):
        self.beginResetModel()
        self._data = new_data
        self.endResetModel()


class ComboDelegate(QItemDelegate):
    """
    A delegate that places a QComboBox in every cell of the given column.
    """

    def __init__(self, parent=None):
        super().__init__(parent)

    def createEditor(self, parent, option, index):
        combobox = QComboBox(parent)
        items = index.data()
        if items and isinstance(items, list):
            # Remove duplicates while preserving order.
            seen = set()
            unique_items = []
            for item in items:
                if item not in seen:
                    seen.add(item)
                    unique_items.append(item)
            combobox.addItems(unique_items)
        return combobox

    def setEditorData(self, editor, index):
        value = index.data()
        if value and isinstance(value, list):
            # Default to the last item in the list.
            editor.setCurrentIndex(len(value) - 1)

    def setModelData(self, editor, model, index):
        # Store the selected text back into the model if needed.
        pass


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("ComboBox Delegate Refresh Example")
        self.resize(400, 250)

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.table_view = QTableView()
        layout.addWidget(self.table_view)

        # Set up the model with initial data.
        self.model = TableModel(INITIAL_DATA)
        self.table_view.setModel(self.model)

        # Assign the ComboBox delegate to column 1 (the "Versions" column).
        delegate = ComboDelegate(self.table_view)
        self.table_view.setItemDelegateForColumn(1, delegate)

        # Open persistent editors so the ComboBoxes are always visible.
        for row in range(self.model.rowCount()):
            index = self.model.index(row, 1)
            self.table_view.openPersistentEditor(index)

        # Buttons to swap data.
        button_layout = QHBoxLayout()
        layout.addLayout(button_layout)

        btn_initial = QPushButton("Load Initial Data")
        btn_initial.clicked.connect(self.load_initial_data)
        button_layout.addWidget(btn_initial)

        btn_updated = QPushButton("Load Updated Data")
        btn_updated.clicked.connect(self.load_updated_data)
        button_layout.addWidget(btn_updated)

    def load_initial_data(self):
        self.model.update_data(INITIAL_DATA)
        self._reopen_editors()

    def load_updated_data(self):
        self.model.update_data(UPDATED_DATA)
        self._reopen_editors()

    def _reopen_editors(self):
        """Re-open persistent editors after a model reset."""
        for row in range(self.model.rowCount()):
            index = self.model.index(row, 1)
            self.table_view.openPersistentEditor(index)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

Run this and you'll see a table with three rows. Each row has a name and a ComboBox filled with version strings. Click Load Updated Data and the ComboBoxes refresh with completely new values. Click Load Initial Data to swap back.

ComboBox delegate refreshing in QTableView after model reset

What's Happening Behind the Scenes

When you call beginResetModel(), the view receives a signal that the model is about to undergo a major change. It discards all its internal state — cached data, persistent editors, selections, everything. When endResetModel() fires, the view rebuilds itself from scratch by querying the model again.

This is why we also need to call openPersistentEditor() again after each reset. Persistent editors are part of the view's state that gets discarded during the reset. If you're using regular (non-persistent) editors — the kind that only appear when a user double-clicks a cell — you don't need to worry about this step, since the editor will be created fresh the next time the user interacts with the cell.

When to Use Model Reset vs. Other Signals

A full model reset is the right tool when a large portion (or all) of your data has changed. If only a single cell's data has changed, emitting dataChanged for that specific index is more efficient and doesn't disrupt the user's current selection or scroll position:

python
# For a single cell change:
index = self.index(row, column)
self.dataChanged.emit(index, index, [Qt.DisplayRole])

For insertions and removals of rows, use beginInsertRows()/endInsertRows() and beginRemoveRows()/endRemoveRows() respectively. These targeted signals let the view update only what's necessary, which keeps things smooth — especially in larger tables.

The full model reset is the "nuclear option," but when your entire dataset is being swapped out (as in this scenario), it's exactly what you want.

Summary

When your QComboBox delegates in a QTableView aren't updating after a data change, the fix is straightforward: wrap your data update in beginResetModel() and endResetModel(). This tells the view to throw away its cached state and rebuild everything from the model, ensuring your delegates pick up the fresh data the next time they create an editor.

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

Bring Your PyQt/PySide Application to Market

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

Martin Fitzpatrick

How to Clear and Refresh ComboBox Delegate Data in QTableView 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.