Schollii Schollii - 3 months ago 15
Python Question

Slot on list widget item data object never called (in PyQt 5.7)

In PyQt 5.5 the following code worked, but not so in PyQt 5.7 (the list shows 'example' rather than 'new example', and indeed debugging shows that the slot is never hit). Does anyone know what is wrong with it:

from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QApplication
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, Qt


class MyListItemData(QObject):
def __init__(self, list_widget_item, obj):
super().__init__()
self.list_widget_item = list_widget_item
obj.sig_name_changed.connect(self.__on_list_item_name_changed)

# @pyqtSlot(str)
def __on_list_item_name_changed(self, new_name: str):
self.list_widget_item.setText(new_name)


class Data(QObject):
sig_name_changed = pyqtSignal(str)


class SearchPanel2(QListWidget):
def __init__(self, parent=None):
QListWidget.__init__(self, parent)
obj = Data()
hit_item = QListWidgetItem('example')
hit_item.setData(Qt.UserRole, MyListItemData(hit_item, obj))
self.addItem(hit_item)
obj.sig_name_changed.emit('new_example')


app = QApplication([])
search = SearchPanel2()
search.show()
app.exec


Although probably not the way this was supposed to be done, in PyQt 5.5 it was an acceptable workaround for a PyQt 5.5 bug (that prevented us from simply deriving from QListWidgetItem so the item could be directly connect to signals).

Post-answer edit

After Ekhumoro answered, I was confronted with a harsh reality: this fixed the example code posted, but not my app, because my app was doing exactly what the solution said to do. So I revisited: in the real app, the items are created later, and the signal for name change is emitted later. Therefore a better minimal example to reproduce my problem would have had the following:

class SearchPanel2(QListWidget):
def __init__(self, obj, parent=None):
QListWidget.__init__(self, parent)

hit_item = QListWidgetItem('example')
data = MyListItemData(hit_item, obj)
hit_item.setData(Qt.UserRole, data) # slot not called

self.addItem(hit_item)
# self.data = data

def emit(self):
obj.sig_name_changed.emit('new_example')


app = QApplication([])
obj = Data()
search = SearchPanel2(obj)
search.show()
QTimer.singleShot(2000, search.emit)
app.exec()

assert search.item(0).text() == 'new_example'


This fails assertion. The assertion passes if data is kept by strong reference (uncomment last line of init). So it is likely that setData() keeps only a weak reference to its second argument, causing data to get deleted at end of init unless it is stored somewhere.

Answer

There seems to be some kind of garbage-collection issue. Try this instead:

    hit_item = QListWidgetItem('example')
    data = MyListItemData(hit_item, obj)
    hit_item.setData(Qt.UserRole, data)