奇怪的发射行为。执行emit时无法调用slot

发布于 2025-01-08 00:53:31 字数 4605 浏览 1 评论 0原文

我在使用 PySide/Qt 时遇到了这种奇怪的情况。

编辑:最后我添加了一个小的“可以运行”代码来测试所经历的行为。如果你愿意,你可以跳过胡言乱语,通过实际代码来看看我在说什么。根据信号/槽机制,它应该表现出意想不到的行为

设计的快速演示。我有一个主窗口,它包含两个视图、两个控制器和两个模型。

这两个视图是在主窗口中创建的,就像这样

    ui.listView = views.ListView(ui.hSplitter)
    ui.threeDView = views.ThreeDView(ui.hSplitter)

没什么花哨的。模型是在 ui 之后创建的

    listModel = models.ListModel()
    listSelectionModel = models.ListSelectionModel()

。第一个模型包含实际数据。第二个包含选择,以便 3D 视图可以检查选择是否已更改(通常通过用户单击列表视图)并以 3D 形式绘制实体。

最后,像这样创建控制器

threeDController = ThreeDController(threeDView=self._ui.threeDView,
                                    listModel=listModel,
                                    listSelectionModel=listSelectionModel)

listController = ListController(listView = self._ui.listView,
                                listModel = listModel,
                                listSelectionModel=listSelectionModel)

然后我在 listModel 中添加一些模拟项目以单击一些内容。

这个想法是列表应该显示项目,当您单击时,3D 视图将显示它们。我的做法如下: ListView 定义如下

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()

请注意我如何为 ListView 定义 itemActivated 信号。我拦截 QListWidget(注意 Q)itemActivated,将其重定向到 ListView 的受保护插槽,然后发出 ListView 信号,该信号将被外部监听。我这样做是因为我希望 ListView 完全包裹其内部控件。

谁在监听这个 ListView itemActivated 事件? ListController 应该获取此信号,并相应地设置选择,以便 3d 控制器可以更新 3d 视图。

这是 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)

正如您所看到的,在控制器中,我连接到 ListView 的信号 itemActivated,并期望看到调用的正确插槽。不幸的是这并没有发生。但是,如果我在占位符处添加以下行

 self._listView.itemActivated.emit()

,也就是说,我在“从外部”连接后强制发出信号(我在控制器中,并且发出视图中定义的信号),我开始接收事件一切都按预期工作:每次单击项目时都会调用 ListController._listViewItemActivated,并且 3d 视图也会随之更新。

我尝试在一个较小的程序中复制这个案例,但我总是得到一些有用的东西。此时此刻,你是我唯一的希望,因为我所经历的行为根本没有意义。

编辑

此代码显示了相同的行为,只是现在即使强制发射也无法解决。尝试单击列表视图中的“hello”元素。它将打印消息,但不会随之发出“胜利!”行将不会被打印。

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_())

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_())

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不离久伴 2025-01-15 00:53:31

我可能是错的,但尝试添加插槽装饰器,例如:

@QtCore.Slot()
def _listViewItemActivated(self):
    identifier = self._listView.currentSelectedId()
    self._selectionModel.setSelected(identifier)

或者只是添加一些虚拟参数来传递?

@QtCore.Slot(int)
...

itemActivated = QtCore.Signal(int)

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)
幸福丶如此 2025-01-15 00:53:31

好吧,这个问题很微妙,我是个傻瓜,没有意识到这一点,因为它毕竟是基本的 python。您必须将控制器存储在 MainWindow 对象上的某个位置,否则一旦 init 结束,它们就会超出范围并消失,因此它们将不再监听信号。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文