Search code examples
pythonpyqtoverridingsignals-slotsqlistview

Overriding virtual protected slot of QListView at runtime


I am using PyQt 5.9.1, and I want to connect QListView.currentChanged to a function at runtime. But for some reason, I can do it only before calling setModel(). If I call setModel() (even with a None argument) beforehand, then my currentChanged function is never called.

# https://www.pythoncentral.io/pyside-pyqt-tutorial-qlistview-and-qstandarditemmodel/
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import  *

def currentChanged(current, previous):
    print(current, previous)


app = QApplication(sys.argv)

model = QStandardItemModel()
foods = [
    'Cookie dough', # Must be store-bought
    'Hummus', # Must be homemade
    'Spaghetti', # Must be saucy
    'Dal makhani', # Must be spicy
    'Chocolate whipped cream' # Must be plentiful
]
for food in foods:
    item = QStandardItem(food)
    model.appendRow(item)

list = QListView()
list.setWindowTitle('Honey-Do List')
list.setMinimumSize(600, 400)
list.currentChanged = currentChanged  # BEFORE setModel
list.setModel(model)

list.show()
app.exec_()

Solution

  • Whenever setModel() is called, a new selection model is created. Internally, the currentChanged signal of the new selection model is then connected to the protected currentChanged slot of the list-view. Thus, whenever a data model is set, the existing internal signal connections are automatically disconnected and reconnected. In your example, when you monkey-patch currentChanged after calling setModel(), the internal signal connections will not be re-made, and so your function will never be called.

    In general, it is usually a mistake to make changes to a view before setting any of its models, because there are likely to be unpredictable internal side-effects which won't always be documented.

    You should also note that it is not necessary to monkey-patch currentChanged at all. The correct way to do things is like this:

    list = QListView()
    list.setModel(model)
    list.selectionModel().currentChanged.connect(currentChanged)
    

    This will work in exactly the same way as your current code.