简体   繁体   English

使用 lambda 表达式连接 pyqt 中的插槽

[英]Using lambda expression to connect slots in pyqt

I am trying to connect slots with lambda functions, but it's not working the way I expect.我正在尝试将插槽与 lambda 函数连接起来,但它没有按我预期的方式工作。 In the code below, I succeed in connecting the first two buttons correctly.在下面的代码中,我成功地正确连接了前两个按钮。 For the second two, which I connect in a loop, this goes wrong.对于我循环连接的后两个,这是错误的。 Someone before me had the same question ( Qt - Connect slot with argument using lambda ), but this solution doesn't work for me.在我之前有人有同样的问题( Qt - Connect slot with argument using lambda ),但这个解决方案对我不起作用。 I've been staring at my screen for a half hour, but I can't figure out how my code is different.我一直盯着我的屏幕半个小时,但我不知道我的代码有什么不同。

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)

Pressing the first two buttons yields 'Pushed button 1' and 'Pushed button 2', the other two both yield 'Pushed button False', although I expected 3 and 4.按下前两个按钮会产生“按下按钮 1”和“按下按钮 2”,另外两个按钮都会产生“按下按钮 False”,尽管我预计会出现 3 和 4。

I also haven't understood the lambda mechanism completely.我也没有完全理解 lambda 机制。 What exactly gets connected?究竟连接了什么? A pointer to a function that is generated by lambda (with the parameter substituted in) or is the lambda function evaluated whenever the signal fires?指向由 lambda 生成的函数的指针(其中替换了参数)还是每当信号触发时评估 lambda 函数?

The QPushButton.clicked signal emits an argument that indicates the state of the button. QPushButton.clicked信号发出一个指示按钮状态的参数。 When you connect to your lambda slot, the optional argument you assign idx to is being overwritten by the state of the button.当您连接到 lambda 插槽时,您分配给idx的可选参数将被按钮的状态覆盖。

Instead, make your connection as相反,将您的连接设为

button.clicked.connect(lambda state, x=idx: self.button_pushed(x))

This way the button state is ignored and the correct value is passed to your method.这样,按钮状态将被忽略,并将正确的值传递给您的方法。

Beware!谨防! As soon as you connect your signal to a lambda slot with a reference to self, your widget will not be garbage-collected!只要您将信号连接到引用 self 的 lambda 槽,您的小部件就不会被垃圾收集! That's because lambda creates a closure with yet another uncollectable reference to the widget.那是因为 lambda 创建了一个闭包,其中包含对小部件的另一个不可收集的引用。

Thus, self.someUIwidget.someSignal.connect(lambda p: self.someMethod(p)) is very evil :)因此, self.someUIwidget.someSignal.connect(lambda p: self.someMethod(p))非常邪恶:)

I'm not honestly sure what's going wrong with your use of lambda here either.老实说,我也不确定您在这里使用 lambda 有什么问题。 I think it's because idx (your loop index when you set up the auto buttons) is going out of scope and doesn't contain the proper value anymore.我认为这是因为 idx (设置自动按钮时的循环索引)超出范围并且不再包含正确的值。

But I don't think you need to do it this way.但我不认为你需要这样做。 It looks like the only reason you're using lambda is so that you can pass an argument to button_pushed(), identifying which button it was.看起来您使用 lambda 的唯一原因是您可以将参数传递给 button_pushed(),以确定它是哪个按钮。 There's a function sender() that can be called in the button_pushed() slot which identifies which button originated the signal.有一个函数sender()可以在 button_pushed() 槽中调用,它标识哪个按钮发出信号。

Here's an example which I think does more or less what you were shooting for:这是一个示例,我认为它或多或少地满足了您的拍摄目的:

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

It's very simple.这很简单。 Check the working code and not working code.检查工作代码而不是工作代码。 You have a syntax error.您有语法错误。

Working code:工作代码:

self.button_1.clicked.connect(lambda x:self.button_pushed(1))

Doesn't work:不起作用:

button.clicked.connect(lambda x=idx: self.button_pushed(x))

Fix:使固定:

button.clicked.connect(lambda x: self.button_pushed(idx))

for lambda you are defining an "x" function and explaining the function to the program as "self.button_pushed(idx)" in order to use arguments with your function in this case the argument is (idx).对于 lambda,您正在定义一个“x”函数并将该函数解释为“self.button_pushed(idx)”,以便在您的函数中使用参数,在这种情况下,参数是 (idx)。 Just try and let me know if it works.试着让我知道它是否有效。

The issue he has is that he is trying to get different outputs from a for loop creation.他的问题是他试图从 for 循环创建中获得不同的输出。 Unfortunately, it assigns the last value to any variable named button, so it gives 4 as a result.不幸的是,它将最后一个值分配给任何名为 button 的变量,因此结果为 4。 First two are working because they are not created in a for loop but created individually.前两个正在工作,因为它们不是在 for 循环中创建的,而是单独创建的。

And the names of the button variables are different in the working ones as button_1 and button_2.并且按钮变量的名称在工作变量中不同,如button_1 和button_2。 In the for loop all buttons created will have the name button which results in the same function.在 for 循环中,创建的所有按钮都将具有名称按钮,从而产生相同的功能。

The solution for what he is trying to do is below and it works like a charm.他正在尝试做的事情的解决方案如下,它就像一个魅力。

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

I put all in one place so that all can see.我把所有东西放在一个地方,让所有人都能看到。 Tell what you want to do is easier than speculations.告诉你想做什么比猜测更容易。 (You can also add new variables in the list in Frame function and it will create more buttons for you with different values and functions.) (您也可以在 Frame 函数的列表中添加新变量,它会为您创建更多具有不同值和功能的按钮。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM