1

I am struggling to get this working. I tried to transpose from a c++ post into python with no joy: QMessageBox with a "Do not show this again" checkbox

my rough code goes like:

from PyQt5 import QtWidgets as qtw

...
mb = qtw.QMessageBox
cb = qtw.QCheckBox

# following 3 lines to get over runtime errors
# trying to pass the types it was asking for
# and surely messing up
mb.setCheckBox(mb(), cb())
cb.setText(cb(), "Don't show this message again")
cb.show(cb())
ret = mb.question(self,
                 'Close application',
                 'Do you really want to quit?',
                 mb.Yes | mb.No )
if ret == mb.No:
   return
self.close()

the above executes with no errors but the checkbox ain't showing (the message box does). consider that I am genetically stupid... and slow, very slow. so please go easy on my learning curve

Caligola
  • 59
  • 9

1 Answers1

3

When trying to "port" code, it's important to know the basis of the source language and have a deeper knowledge of the target.

For instance, taking the first lines of your code and the referenced question:

QCheckBox *cb = new QCheckBox("Okay I understand");

The line above in C++ means that a new object (cb) of type QCheckBox is being created, and it's assigned the result of QCheckBox(...), which returns an instance of that class. To clarify how objects are declared, here's how a simple integer variable is created:

int mynumber = 10

This is because C++, like many languages, requires the object type for its declaration.

In Python, which is a dynamic typing language, this is not required (but it is possible since Python 3.6), but you still need to create the instance, and this is achieved by using the parentheses on the class (which results in calling it and causes both calling __new__ and then __init__). The first two lines of your code then should be:

mb = qtw.QMessageBox()
cb = qtw.QCheckBox()

Then, the problem is that you're calling the other methods with new instances of the above classes everytime.

An instance method (such as setCheckBox) is implicitly called with the instance as first argument, commonly known as self.

checkboxInstance = QCheckBox()
checkboxInstance.setText('My checkbox')
# is actually the result of:
QCheckBox.setText(checkboxInstance, 'My checkbox')

The last line means, more or less: call the setText function of the class QCheckBox, using the instance and the text as its arguments. In fact, if QCheckBox was an actual python class, setText() would look like this:

class QCheckBox:
    def setText(self, text):
        self.text = text

When you did cb = qtw.QCheckBox you only created another reference to the class, and everytime you do cb() you create a new instance; the same happens for mb, since you created another reference to the message box class.

The following line:

mb.setCheckBox(mb(), cb())

is the same as:

QMessageBox.setCheckBox(QMessageBox(), QCheckBox())

Since you're creating new instances every time, the result is absolutely nothing: there's no reference to the new instances, and they will get immediately discarded ("garbage collected", aka, deleted) after that line is processed.

This is how the above should actually be done:

mb = qtw.QMessageBox()
cb = qtw.QCheckBox()

mb.setCheckBox(cb)
cb.setText("Don't show this message again")

Now, there's a fundamental flaw in your code: question() is a static method (actually, for Python, it's more of a class method). Static and class methods are functions that don't act on an instance, but only on/for a class. Static methods of QMessageBox like question or warning create a new instance of QMessageBox using the provided arguments, so everything you've done before on the instance you created is completely ignored.

These methods are convenience functions that allow simple creation of message boxes without the need to write too much code. Since those methods only allow customization based on their arguments (which don't include adding a check box), you obviously cannot use them, and you must code what they do "under the hood" explicitly.

Here is how the final code should look:

    # create the dialog with a parent, which will make it *modal*
    mb = qtw.QMessageBox(self)
    mb.setWindowTitle('Close application') 
    mb.setText('Do you really want to quit?') 
    # you can set the text on a checkbox directly from its constructor
    cb = qtw.QCheckBox("Don't show this message again")
    mb.setCheckBox(cb)
    mb.setStandardButtons(mb.Yes | mb.No)
    ret = mb.exec_()
    # call some function that stores the checkbox state
    self.storeCloseWarning(cb.isChecked())
    if ret == mb.No:
        return
    self.close()
musicamante
  • 41,230
  • 6
  • 33
  • 58
  • bloody hell! cheers mate, I wasn't expecting a full tutorial. You rock! seriously, no irony or pun intended. Thnx for taking the time to explain in details, like to a 10-years-old. That's what I was after but surely not expecting it. Getting the chance to understand stuff under the hood, explained in plain english, and not just copy-pasting is refreshing. Have a fantastic weekend mr music lover. – Caligola Apr 23 '21 at 16:30
  • and then I just realized you from booty-land too. then: greetings from Genova sir. we'll se the end of the tunnel... at some point. – Caligola Apr 23 '21 at 16:33
  • @Caligola you're very welcome! Let's hope so... Greetings from Como! – musicamante Apr 23 '21 at 17:29