我有一个任务栏菜单,单击该菜单时会连接到获取触发事件的插槽。现在的问题是我想知道单击了哪个菜单项,但我不知道如何将该信息发送到连接的函数。这是用于将操作连接到函数的:
QtCore.QObject.connect(menuAction, 'triggered()', menuClickedFunc)
我知道有些事件会返回一个值,但triggered()不会。那么我该如何实现这一点呢?我必须自己发出信号吗?
使用
lambda
这是 PyQt 书中的示例:
self.connect(button3, SIGNAL("clicked()"),
lambda who="Three": self.anyButton(who))
顺便说一句,你也可以使用
functools.partial
,但我发现lambda
的方法更简单、更清晰。
正如已经提到的这里,您可以使用 lambda 函数将额外的参数传递给您想要执行的方法。
在此示例中,您可以将字符串 obj 传递给按下按钮时调用的函数 AddControl()。
# Create the build button with its caption
self.build_button = QPushButton('&Build Greeting', self)
# Connect the button's clicked signal to AddControl
self.build_button.clicked.connect(lambda: self.AddControl('fooData'))
def AddControl(self, name):
print name
我还想补充一点,如果您只需要找出哪个小部件发送了信号,您可以使用
sender
方法。例如:
def menuClickedFunc(self):
# The sender object:
sender = self.sender()
# The sender object's name:
senderName = sender.objectName()
print senderName
使用 functools.partial
否则,如果你使用 lambda,你会发现在脚本运行时无法动态传递参数。
一般来说,您应该将每个菜单项连接到不同的插槽,并且让每个插槽仅处理其自己的菜单项的功能。例如,如果您有“保存”、“关闭”、“打开”等菜单项,则应该为每个菜单项创建一个单独的插槽,而不是尝试在其中包含一个带有 case 语句的插槽。
如果您不想这样做,您可以使用 QObject::sender() 函数来获取指向发送者的指针(即:发出信号的对象)。不过,我想更多地了解您想要实现的目标。
其他答案中建议的方法有问题,
self.whatever.connect(lambda x: self.method(..., x)) # approach 1 (suboptimal)
self.whatever.connect(functools.partial(self.method, ...)) # approach 2 (suboptimal)
它们创建了一个引用循环:
self
对象持有对(或者是)带有信号的对象的引用,该对象持有对函数或partial
对象的引用,该对象持有对self
的引用
对象。结果是(在 CPython 中)当所有其他对这些对象的引用消失时,这些对象都不会被垃圾回收;它们只会在下次循环收集器运行时被收集。反过来,他们将保留他们引用的所有其他 Python 数据结构以及他们共同拥有的任何 Qt 对象。这并不完全是内存泄漏,因为所有内容最终都会被释放,但这可能是一个问题。
这样写就没有引用循环
self.whatever.connect(self.method)
因为在 PyQt 和 PySide 中,
connect
对于 Python 绑定方法对象有一个特殊情况:它不是保存对绑定方法的引用,而是提取其两个字段(__self__
和 __func__
)并保存弱引用到 __self__
以及对 __func__
的普通引用。如果 __self__
消失,连接将自动断开。
您可以通过编写以下内容来利用内联
lambda
函数的行为:
self.whatever.connect((lambda obj, x: obj.method(..., x)).__get__(self)) # approach 1' (better)
__get__
是创建绑定方法对象的函数对象的方法。
您可以通过编写
functools.partial
的替代品来让这变得不那么尴尬,它返回正确的魔法类型的对象:
def partial_bound_method(bound_method, *args, **kwargs):
f = functools.partialmethod(bound_method.__func__, *args, **kwargs)
# NB: the seemingly redundant lambda is needed to ensure the correct result type
return (lambda *args: f(*args)).__get__(bound_method.__self__)
...
self.whatever.connect(partial_bound_method(self.method, ...)) # approach 2' (better)
这是一个测试,它是否按预期工作:
# replace PyQt5 with PyQt6/PySide2/PySide6 as appropriate
from PyQt5.QtCore import QObject, QCoreApplication
import functools, weakref
def partial_bound_method(bound_method, *args, **kwargs):
f = functools.partialmethod(bound_method.__func__, *args, **kwargs)
# NB: the seemingly redundant lambda is needed to ensure the correct result type
return (lambda *args: f(*args)).__get__(bound_method.__self__)
app = QCoreApplication([])
class Class(QObject):
def method(*args): pass
def test(maketarget):
obj = Class()
# the signal doesn't matter; this is one that happens to exist in QObject
obj.objectNameChanged.connect(maketarget(obj))
obj = weakref.ref(obj)
print('not freed' if obj() else 'freed')
test(lambda obj: obj.method)
test(lambda obj: lambda *args: obj.method('x', *args))
test(lambda obj: functools.partial(obj.method, 'x'))
test(lambda obj: partial_bound_method(obj.method, 'x'))
应该打印出来
freed
not freed
not freed
freed