PyQt 5.5 では次のコードが機能しましたが、PyQt 5.7 では機能しませんでした (リストには「新しい例」ではなく「例」が表示され、実際にデバッグするとスロットがヒットしないことが示されます)。誰がそれの何が悪いのか知っていますか:
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
おそらくこれが行われるはずの方法ではありませんが、PyQt 5.5 では、PyQt 5.5 のバグ (QListWidgetItem から単純に派生させることができず、アイテムをシグナルに直接接続できなかった) の許容できる回避策でした。
回答後の編集
Ekhumoro が回答した後、私は厳しい現実に直面しました。これにより、投稿されたサンプル コードは修正されましたが、私のアプリでは修正されませんでした。そこで再訪しました。実際のアプリでは、アイテムは後で作成され、名前変更のシグナルは後で発行されます。したがって、私の問題を再現するためのより良い最小限の例は次のとおりです。
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'
これはアサーションに失敗します。データが強い参照によって保持されている場合、アサーションはパスします (init の最後の行のコメントを外します)。そのため、setData() は 2 番目の引数への弱い参照のみを保持する可能性が高く、データがどこかに保存されていない限り、init の最後にデータが削除されます。