使用 lambda 表达式连接 pyqt 中的插槽
Posted
技术标签:
【中文标题】使用 lambda 表达式连接 pyqt 中的插槽【英文标题】:Using lambda expression to connect slots in pyqt 【发布时间】:2016-06-19 13:47:32 【问题描述】:我正在尝试将插槽与 lambda 函数连接起来,但它没有按我预期的方式工作。在下面的代码中,我成功地正确连接了前两个按钮。对于我循环连接的后两个,这是错误的。在我之前有人有同样的问题 (Qt - Connect slot with argument using lambda),但这个解决方案对我不起作用。我盯着屏幕看了半个小时,但我不知道我的代码有何不同。
class MainWindow(QtGui.QWidget):
def __init__(self):
super(QtGui.QWidget, self).__init__()
main_layout = QtGui.QVBoxLayout(self)
# Works:
self.button_1 = QtGui.QPushButton('Button 1 manual', self)
self.button_2 = QtGui.QPushButton('Button 2 manual', self)
main_layout.addWidget(self.button_1)
main_layout.addWidget(self.button_2)
self.button_1.clicked.connect(lambda x:self.button_pushed(1))
self.button_2.clicked.connect(lambda x:self.button_pushed(2))
# Doesn't work:
self.buttons = []
for idx in [3, 4]:
button = QtGui.QPushButton('Button auto'.format(idx), self)
button.clicked.connect(lambda x=idx: self.button_pushed(x))
self.buttons.append(button)
main_layout.addWidget(button)
def button_pushed(self, num):
print 'Pushed button '.format(num)
按下前两个按钮会产生“Pushed button 1”和“Pushed button 2”,其他两个都产生“Pushed button False”,尽管我预计会出现 3 和 4。
我也没有完全理解 lambda 机制。究竟是什么联系起来的?指向由 lambda 生成的函数的指针(参数被替换)还是在信号触发时评估 lambda 函数?
【问题讨论】:
【参考方案1】:QPushButton.clicked
信号发出一个指示按钮状态的参数。当您连接到您的 lambda 插槽时,您分配给 idx
的可选参数将被按钮的状态覆盖。
相反,将您的连接设为
button.clicked.connect(lambda state, x=idx: self.button_pushed(x))
这样按钮状态被忽略,正确的值被传递给你的方法。
【讨论】:
“状态”这个词的真正含义是什么? @ioaniatr 当您连接到clicked
信号时,该信号具有描述为here 的签名。如您所见,当发出信号时,会提供一个参数,其中包含按钮的状态(按钮是否被选中)。我的代码中的变量可以随心所欲地调用,但它必须存在以便 Qt 不会覆盖下一个参数 (x=idx
) 并处于选中状态。
当我尝试从 QMenu.addAction() 连接一个 QAction 时,我得到一个异常:missing 1 required positional argument: 'checked'
触发我的菜单项时。 action.triggered.connect(lambda checked, service=service: printService(service))
我做错了什么?
@grego,如果你还没有解决这个问题。我建议使用 functools.partial:action.triggered.connect(functools.partial(printService, service))
【参考方案2】:
小心!只要您将信号连接到引用 self 的 lambda 插槽,您的小部件就不会被垃圾收集!这是因为 lambda 创建了一个闭包,其中包含对小部件的另一个无法收集的引用。
因此,self.someUIwidget.someSignal.connect(lambda p: self.someMethod(p))
非常邪恶:)
【讨论】:
我花了两天时间对代码进行注释和取消注释,结果发现这是我正在调查的“泄漏”错误,然后我遇到了您的分析,这正是我最终得出的结论。现在关闭以更改所有现有代码.... :( 记录一下,如果lambda
将self
传递给某个外部函数,同样的问题:self.someUIwidget.someSignal.connect(lambda someNonSelfMethod(self))
那有什么替代方案?
@Esostack:取决于您想要实现的目标。如果您只想在槽中检测哪个对象发出了信号,可以使用QObject.sender()
方法。此外,您可以将信息存储在小部件属性中并在插槽函数中访问它们,请参见此示例:***.com/questions/49446832/…
@Rhdr 另一个值得一提的讨论在这里***.com/questions/33304203/…【参考方案3】:
老实说,我也不确定您在这里使用 lambda 出了什么问题。我认为这是因为 idx(设置自动按钮时的循环索引)超出范围并且不再包含正确的值。
但我认为你不需要这样做。看起来您使用 lambda 的唯一原因是您可以将参数传递给 button_pushed(),以确定它是哪个按钮。有一个函数 sender()
可以在 button_pushed() 槽中调用,它标识哪个按钮发出了信号。
这里有一个例子,我认为它或多或少符合你的目标:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class MainWindow(QWidget):
def __init__(self):
super(QWidget, self).__init__()
main_layout = QVBoxLayout(self)
self.buttons = []
# Works:
self.button_1 = QPushButton('Button 1 manual', self)
main_layout.addWidget(self.button_1)
self.buttons.append(self.button_1)
self.button_1.clicked.connect(self.button_pushed)
self.button_2 = QPushButton('Button 2 manual', self)
main_layout.addWidget(self.button_2)
self.buttons.append(self.button_2)
self.button_2.clicked.connect(self.button_pushed)
# Doesn't work:
for idx in [3, 4]:
button = QPushButton('Button auto'.format(idx), self)
button.clicked.connect(self.button_pushed)
self.buttons.append(button)
main_layout.addWidget(button)
def button_pushed(self):
print('Pushed button '.format(self.buttons.index(self.sender())+1))
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
【讨论】:
(显然,创建所有按钮“自动”(在循环中)是微不足道的,而不是在循环外创建两个按钮,在循环内创建两个按钮,如果这是您的最终目标... 感谢您的建议,但上面的 three_pineapples 解决方案正是我所寻找的,并解释了发生了什么。是的,没有理由不在循环中生成所有四个按钮,除了展示循环外行为如何不同的示例。 我也很高兴知道你的做法出了什么问题。【参考方案4】:这很简单。检查工作代码和不工作代码。您有语法错误。
工作代码:
self.button_1.clicked.connect(lambda x:self.button_pushed(1))
不起作用:
button.clicked.connect(lambda x=idx: self.button_pushed(x))
修复:
button.clicked.connect(lambda x: self.button_pushed(idx))
对于 lambda,您正在定义一个“x”函数并将该函数解释为“self.button_pushed(idx)”,以便在这种情况下使用参数与您的函数一起使用 (idx)。请尝试让我知道它是否有效。
他的问题是他试图从 for 循环创建中获得不同的输出。不幸的是,它将最后一个值分配给任何名为 button 的变量,因此结果为 4。前两个正在工作,因为它们不是在 for 循环中创建的,而是单独创建的。
并且按钮变量的名称在工作的button_1和button_2中是不同的。在 for 循环中,所有创建的按钮都将具有相同功能的名称按钮。
他正在尝试做的解决方案如下,它就像一个魅力。
from sys import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
buttons = []
def newWin():
window = QWidget()
window.setWindowTitle("Lambda Loop")
window.setFixedWidth(1000)
window.move(175, 10)
window.setStyleSheet("background: #161219;")
grid = QGridLayout()
return window, grid
def newButton(text :str, margin_left, margin_right, x):
button = QPushButton(text)
button.setCursor(QCursor(Qt.PointingHandCursor))
button.setFixedWidth(485)
button.setStyleSheet(
"*border: 4px solid '#BC006C';" +
"margin-left: " + str(margin_left) + "px;" +
"margin-right: " + str(margin_right) + "px;" +
"color: 'white';" +
"font-family: 'Comic Sans MS';" +
"font-size: 16px;" +
"border-radius: 25px;" +
"padding: 15px 0px;" +
"margin-top: 20px;" +
"*:hover background: '#BC006C'"
)
def pushed():
val = x
text = QLabel(str(val))
text.setAlignment(Qt.AlignRight)
text.setStyleSheet(
"font-size: 35px;" +
"color: 'white';" +
"padding: 15px 15px 15px 25px;" +
"margin: 50px;" +
"background: '#64A314';" +
"border: 1px solid '#64A314';" +
"border-radius: 0px;"
)
grid.addWidget(text, 1, 0)
button.clicked.connect(pushed)
return button
app = QApplication(argv)
window, grid = newWin()
def frame1(grid):
for each in [3, 4]:
button = newButton('Button '.format(each), 150, 150, each)
buttons.append(button)
pass
b_idx = 0
for each in buttons:
grid.addWidget(each, 0, b_idx, 1, 2)
b_idx += 1
frame1(grid)
window.setLayout(grid)
window.show()
exit(app.exec())
我把所有东西放在一个地方,以便所有人都能看到。告诉你想做什么比猜测更容易。 (您也可以在 Frame 函数的列表中添加新变量,它会为您创建更多具有不同值和功能的按钮。)
【讨论】:
避免争论选票。投票是私人的,不需要证明是合理的。如果您收到紫外线,您会提出同样的要求吗?不,同样的规则适用于 DV。如果您在帖子中放置不相关的信息,那么这将被视为噪音。如果你的帖子很好,那么很长一段时间,你会得到比 DV 更多的紫外线,如果不是,那么你会得到相反的结果。 另一方面,我在您的答案中没有做出更好的贡献,因为它是已接受答案的不太好的版本,因为例如您的解决方案在 for 循环中失败,这与已接受的答案不同那个案子。 显然你投了反对票。我不是在争论,但投票给人们是不公平的,尤其是像我这样新注册的人,因为我已经做出了真实的陈述。先试试代码。 1.我没有投票,如果我投票了,我也不会这么说。这是任何用户的权利。 2. 投票是由帖子给出的,而不是像所有 SO 规则所表明的那样由用户给出。在这里,我们不根据创建帖子的人来评价帖子,而是根据帖子本身的质量来评价帖子。在这里,无论您是新用户还是老用户,无论您是初学者还是专家,都无关紧要,因为帖子的贡献。请阅读How to Answer 并查看tour以上是关于使用 lambda 表达式连接 pyqt 中的插槽的主要内容,如果未能解决你的问题,请参考以下文章
Pyqt将插槽连接到具有默认参数的函数而没有lambda [重复]
使用PyQt中的QCheckBox或QComboBox更改小部件可见性