Reputation: 143815
I have this strange situation with PySide/Qt.
EDIT: I added a small "can run" code to test the experienced behavior, at the end. If you want, you can skip the babbling and see what I am talking about through actual code. It should behave unexpectedly according to the signal/slot mechanism
Quick presentation of the design. I have a MainWindow, which holds two views, two controllers and two models.
The two views are created in the MainWindow, like this
ui.listView = views.ListView(ui.hSplitter)
ui.threeDView = views.ThreeDView(ui.hSplitter)
Nothing fancy. The models are created after the ui
listModel = models.ListModel()
listSelectionModel = models.ListSelectionModel()
The first one contains the actual data. The second one contains the selection, so that the 3D view can check if the selection has changed (normally by the user clicking on the listView) and plot the entity in 3d.
Finally, the controller are created like this
threeDController = ThreeDController(threeDView=self._ui.threeDView,
listModel=listModel,
listSelectionModel=listSelectionModel)
listController = ListController(listView = self._ui.listView,
listModel = listModel,
listSelectionModel=listSelectionModel)
Then I add some mock items in the listModel to have something to click.
The idea is that the list should show the items, and when you click, the 3d view will display them. I did as follows: ListView is defined as this
class ListView(QtGui.QWidget):
itemActivated = QtCore.Signal()
def __init__(self, parent):
super(ListView, self).__init__(parent)
self._ui = self._createUi()
self._ui.qListWidget.itemActivated.connect(self._itemActivatedSlot)
def _createUi(self):
ui = UIElems()
hLayout = QtGui.QHBoxLayout(self)
ui.qListWidget = QtGui.QListWidget(self)
hLayout.addWidget(ui.qListWidget)
return ui
def _itemActivatedSlot(self, listitem):
# I CONFIRM THIS LINE IS EXECUTED
# SO THE EMIT IS CORRECTLY PERFORMED
self.itemActivated.emit()
Note how I defined an itemActivated signal for the ListView. I intercept the QListWidget (note the Q) itemActivated, redirect it to a protected slot of ListView, and then emit a ListView signal, which will be listened externally. I do this because I want my ListView to wrap completely its internal controls.
Who is listening to this ListView itemActivated event? The ListController is, which should get this signal, and set the selection accordingly, so that the 3d controller can update the 3d view.
This is the ListController
class ListController(QtCore.QObject):
def __init__(self, listView, listModel, selectionModel):
super(ListController, self).__init__()
self._listView = listView
self._listModel = listModel
self._selectionModel = selectionModel
self._listModel.changed.connect(self._listModelChanged)
# HERE IS THE PROBLEM
self._listView.itemActivated.connect(self._listViewItemActivated)
# PLACEHOLDER
def _listModelChanged(self):
self._listView.setItems(self._listModel.items())
def _listViewItemActivated(self): ### ... AND HERE
identifier = self._listView.currentSelectedId()
self._selectionModel.setSelected(identifier)
As you can see, in the controller, I connect to the ListView's signal itemActivated, and expect to see the proper slot called. This unfortunately does not happen. If I, however, add the following line at the placeholder
self._listView.itemActivated.emit()
that is, I force the emitting right after the connect "from outside" (I am in the controller, and I emit a signal defined in the view), I start receiving the events and everything works as expected: the ListController._listViewItemActivated is called every time I click on the items, and the 3d view is consequently updated.
I tried to replicate the case in a smaller program, but I always got something that works. At this point, you are my only hope, because the behavior I experience makes no sense at all.
EDIT
This code shows the same behavior, except that now even the forced emit does not solve. Try to click the "hello" element in the listview. It will print the message, but the emit will not follow and the "Victory!" line will not be printed.
import sys
from PySide import QtGui, QtCore
class ListView(QtGui.QWidget):
itemActivated = QtCore.Signal()
def __init__(self, parent):
super(ListView, self).__init__(parent)
qlistview = QtGui.QListWidget(self)
qlistview.addItem("hello")
qlistview.itemActivated.connect(self._itemActivatedSlot)
def _itemActivatedSlot(self, listitem):
print "Activated "+ listitem.text() # this is printed.
self.itemActivated.emit() # Why not emitted or received ?!?!
class ListController(QtCore.QObject):
def __init__(self, listView):
super(ListController, self).__init__()
self._listView = listView
self._listView.itemActivated.connect(self._listViewItemActivated)
# self._listView.itemActivated.emit() # uncomment to see that the signal is actually connected and the mechanism works
def _listViewItemActivated(self):
print "_listViewItemActivated activated!!! Victory!"
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
listView = ListView(self)
listController = ListController(listView)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setApplicationName("Test")
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
Upvotes: 1
Views: 525
Reputation: 143815
Ok, the problem was subtle, and I'm a fool not realizing it, because it's basic python after all. You have to store the controllers somewhere on the MainWindow object, otherwise as soon as init is over, they will go out of scope and disappear, so they won't listen to signals anymore.
Upvotes: 1
Reputation: 5488
I might be wrong, but try to add slot decorator like:
@QtCore.Slot()
def _listViewItemActivated(self):
identifier = self._listView.currentSelectedId()
self._selectionModel.setSelected(identifier)
Or maybe just adding some dummy parameter to pass?
@QtCore.Slot(int)
...
itemActivated = QtCore.Signal(int)
Upvotes: 2