Stefano Borini
Stefano Borini

Reputation: 143815

Strange emit behavior. Unable to get slot called when emit is performed

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

Answers (2)

Stefano Borini
Stefano Borini

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

Fuxi
Fuxi

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

Related Questions