0

I thought I'll try pyqt(5) for a change and wanted to create a simple dialog on startup of a script that let's the user choose one of three options.

My goal is a popup like this (on startup of a script) that will close on pushing one of the buttons and returns the value of the button that was pressed:

window on startup

And this is how I tried to implement it:

from PyQt5.QtWidgets import QApplication, QDialog, QPushButton, QDialogButtonBox

input_db_connection="None"

class MyDialog(QDialog):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Choose Database connection")
        self.buttonBox=QDialogButtonBox(self)
    
        for option in ["Production Read", "Production Read&Write", "Development"]:
            b = QPushButton(option)
            b.clicked.connect(lambda x: return_and_close(option))
            self.buttonBox.addButton(b,0)
            del b
    
        def return_and_close(value):
            global input_db_connection
            print(value)
            input_db_connection=value
            self.close()

app = QApplication([])   
dg = MyDialog()
dg.show()
app.exec()

print(input_db_connection)
  1. I used a global variable, as I didn't find a way to return anything from the QApplication.

  2. No matter which button I press, the output is "Development", the last option that was entered?

1 Answer 1

2

The main problem is in the lambda scope: what is inside lambda: is evaluated only when it is executed.
When the button is clicked, the for loop has already completed, and at that point option will be the last value assigned in the loop.

The solution is to "force" keyword arguments so that there is a persistent reference in the inner scope. Note that the clicked signal of Qt buttons has a checked argument, and you must consider that in the lambda positional arguments.

Then, instead of creating a global variable (which is almost always a bad choice), you should properly use the functions of QDialog:

  1. use exec(), which blocks execution (but not the event loop) until the dialog returns;
  2. use done() to set a valid result for the dialog and closes it by returning that result;
from PyQt5.QtWidgets import *

class MyDialog(QDialog):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Choose Database connection")
        layout = QVBoxLayout(self)
        self.buttonBox = QDialogButtonBox(self)
        layout.addWidget(self.buttonBox)

        states = [
            (0, "Production Read"), 
            (1, "Production Read&Write"), 
            (2, "Development"), 
        ]

        for value, option in states:
            b = QPushButton(option)
            b.clicked.connect(lambda _, value=value: self.done(value))
            self.buttonBox.addButton(b, 0)


app = QApplication([])   
dg = MyDialog()
print(dg.exec())

Further notes:

  • always use layout managers, creating child widgets is insufficient, and trying to set manual geometries is still not enough unless you have deep knowledge and profound experience about Qt events, size hints and policies;
  • do not call del unnecessarily (in this case it's also completely pointless, as it only deletes the python reference, and being it a local variable that would be overwritten at each for cycle and garbage collected when __init__ returns, it's useless);
  • avoid local functions whenever possible, and always prefer instance methods instead;
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, this works perfectly. One minor detail: If I simply close the dialog, dg.exec() also returns 0. Therefore I would start enumerating the states with 1.
@Engensmax yeah, I had to leave in a hurry and forgot to fix that, sorry. The return code for rejected dialogs is normally 0, that's correct. Another possibility could be to set an attribute that can be recalled, like if dg.exec(): print(dg.selectedMode()) so that you can leave the default QDialog behavior intact.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.