奇怪的发射行为。执行emit时无法调用slot
我在使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我可能是错的,但尝试添加插槽装饰器,例如:
或者只是添加一些虚拟参数来传递?
I might be wrong, but try to add slot decorator like:
Or maybe just adding some dummy parameter to pass?
好吧,这个问题很微妙,我是个傻瓜,没有意识到这一点,因为它毕竟是基本的 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.